python 每行处理速度 VS 块中处理速度
python speed processing per line VS in chunk
我正在尝试对一个巨大的文件执行非常简单的计算,例如计算某些列的标签数量或其他列的平均值和标准偏差。该文件太大,无法放入内存,我目前正在按行处理它:
unique = {key: [] for key in categorical_keys}
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key, value in row.iteritems():
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
现在这似乎太慢了,我想知道在适合内存的块中处理它是否会更快。会更快吗?如果是,为什么?
感谢您的帮助。
循环优化
如果您需要提高速度:
- 请确定,您确实需要加速(否则您会在无价值的任务上花费太多时间)
- 从循环开始
- 检查是否可以防止某些循环
- optimize/remove 循环内指令
- 每条指令都很重要
- 每个引用计数
这是我的优化代码草稿(未测试):
from collections import defaultdict
unique = defaultdict(set)
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key in categorical_keys:
unique[key].add(row[key])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[key])
mean_val = (means[key]*i + value)/(i+1)
means[key] = mean_val
# following fails for i < = 1 with ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-mead_val)**2)/i
except (ValueError, ZeroDivisionError):
pass
收集唯一值
您将 dict 与唯一值列表一起使用:
unique = {key: [] for key in categorical_keys}
并将唯一值作为列表项添加到它(发生在循环内):
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
如果您直接将该值添加到集合中,您可以安全地进行一些测试(如果该值存在于列表中)-
该集合会注意,那里只收集唯一值。
使用 defaultdict
将确保,如果您使用任何键,您已经有一些空集,
尚未使用。
不在每个循环中测试记录键的类型,提前知道它们
您的代码重复循环记录键并测试它们的类型,然后执行某些操作:
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
如果您的 categorical_keys
和 numerical_keys
正确设置为
现有值。然后就可以直接循环遍历已知键名了:
for key in categorical_keys:
unique[key].add(row[value])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[value])
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
重新使用一次计算值
您的代码重复计算值:
float(value)
做一次重复使用。
另外mean[key]
的值也计算一次并设置到means[key]
,并且
两行之后再次使用该值。最好将值存储在局部变量中
并使用它两次。任何查找(如 means[key]
)都需要一些费用。
捕获异常通常比值测试快
您的代码测试值是否为空:
elif key in numerical_keys:
if value:
# something here
您可以将其替换为直接与值一起使用的代码。如果值错误,它将
失败和异常 ValueError
将被捕获并忽略。如果您设置了大多数值,这将加快速度
向上。
try:
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
你能阻止 if i > 1:
测试吗?
这个条件在大多数情况下是正确的,但是你在每个循环中测试它。如果你找到一种方法(我没有)
阻止这个测试,你也可以更快地得到它。
正如您所建议的,我们可以通过捕获 ZeroDivisionError
for i <= 1:
来解决它
try:
# shall throw ValueError if None or empty string
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
# for i <= 1 shall raise ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except (ValueError, ZeroDivisionError):
pass
正在分块处理数据
关于分块处理:
- 它确实增加了一些复杂性
- 如果小心操作,它可能会加快程序速度
- 它可能会减慢速度或提供错误的结果
分块可以提高速度的地方
读取较大的文件
这听起来很明显,但图书馆已经解决了。期待轻微或 none 改进。
获取块中的 CSV 记录
我不知道 csv.reader
或 csv.DictReader
的方法允许获取
直接记录大块。你必须自己做。这是可能的,我
建议使用 itertools.groupby
.
不要指望它本身的加速(它会减慢一点),但它是以后其他基于块的加速的先决条件。
将值块添加到集合中
代码(当前)将值一个一个地添加到集合中。如果你用块做
值(越多越好),它会更快,因为每个 python 调用都有
一些小开销。
计算平均值和标准差值
你可以利用 statistics
包,它可能有
优化代码(但它似乎是纯粹的 python)。
无论如何,因为您要分块处理数据,很简单
statistics.mean
对您不起作用,否则您必须汇总
结果以某种方式在一起(如果可能的话)。
如果你自己计算这个值,通过仔细编码你可以得到一些
加速,主要基于事实,你直接在一个块中获取值并且
不必在每个循环中逐个取消引用值。
分块结论
对我来说,分块优化似乎太复杂了,很难做到
预测,如果它带来任何价值。
Jan 的回答很好,但是如果你想加快速度,你可以在下面进行自己的研究:
执行的逐行分析:如果你不知道什么地方慢,你就无法进一步改进 (https://github.com/rkern/line_profiler)
由于您的大部分代码都是数值运算,您可能会在类型检查开销上浪费大量时间。这里有两条路径 - 用类型装饰代码并使用 Cython, or leave the code as it is and use Pypy - 两者都应该给你一个很好的提升。
我正在尝试对一个巨大的文件执行非常简单的计算,例如计算某些列的标签数量或其他列的平均值和标准偏差。该文件太大,无法放入内存,我目前正在按行处理它:
unique = {key: [] for key in categorical_keys}
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key, value in row.iteritems():
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
现在这似乎太慢了,我想知道在适合内存的块中处理它是否会更快。会更快吗?如果是,为什么?
感谢您的帮助。
循环优化
如果您需要提高速度:
- 请确定,您确实需要加速(否则您会在无价值的任务上花费太多时间)
- 从循环开始
- 检查是否可以防止某些循环
- optimize/remove 循环内指令
- 每条指令都很重要
- 每个引用计数
这是我的优化代码草稿(未测试):
from collections import defaultdict
unique = defaultdict(set)
means = {key: 0.0 for key in numerical_keys}
sds = {key: 0.0 for key in numerical_keys}
with open('input/train.csv', 'r') as read_file:
reader = csv.DictReader(read_file, delimiter=',', quotechar='|')
for i, row in enumerate(reader):
for key in categorical_keys:
unique[key].add(row[key])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[key])
mean_val = (means[key]*i + value)/(i+1)
means[key] = mean_val
# following fails for i < = 1 with ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-mead_val)**2)/i
except (ValueError, ZeroDivisionError):
pass
收集唯一值
您将 dict 与唯一值列表一起使用:
unique = {key: [] for key in categorical_keys}
并将唯一值作为列表项添加到它(发生在循环内):
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
如果您直接将该值添加到集合中,您可以安全地进行一些测试(如果该值存在于列表中)- 该集合会注意,那里只收集唯一值。
使用 defaultdict
将确保,如果您使用任何键,您已经有一些空集,
尚未使用。
不在每个循环中测试记录键的类型,提前知道它们
您的代码重复循环记录键并测试它们的类型,然后执行某些操作:
if key in categorical_keys:
if row[key] not in unique[key]:
unique[key].extend([value])
elif key in numerical_keys:
if value:
means[key] = (means[key]*i + float(value))/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (float(value)-means[key])**2)/i
如果您的 categorical_keys
和 numerical_keys
正确设置为
现有值。然后就可以直接循环遍历已知键名了:
for key in categorical_keys:
unique[key].add(row[value])
for key in numerical_keys:
try:
# shall throw ValueError if None or empty string
value=float(row[value])
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
重新使用一次计算值
您的代码重复计算值:
float(value)
做一次重复使用。
另外mean[key]
的值也计算一次并设置到means[key]
,并且
两行之后再次使用该值。最好将值存储在局部变量中
并使用它两次。任何查找(如 means[key]
)都需要一些费用。
捕获异常通常比值测试快
您的代码测试值是否为空:
elif key in numerical_keys:
if value:
# something here
您可以将其替换为直接与值一起使用的代码。如果值错误,它将
失败和异常 ValueError
将被捕获并忽略。如果您设置了大多数值,这将加快速度
向上。
try:
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
if i > 1:
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except ValueError:
pass
你能阻止 if i > 1:
测试吗?
这个条件在大多数情况下是正确的,但是你在每个循环中测试它。如果你找到一种方法(我没有) 阻止这个测试,你也可以更快地得到它。
正如您所建议的,我们可以通过捕获 ZeroDivisionError
for i <= 1:
try:
# shall throw ValueError if None or empty string
value=float(value)
means[key] = (means[key]*i + value)/(i+1)
# for i <= 1 shall raise ZeroDivisionError
sds[key] = (sds[key]*(i-1) + (value-means[key])**2)/i
except (ValueError, ZeroDivisionError):
pass
正在分块处理数据
关于分块处理:
- 它确实增加了一些复杂性
- 如果小心操作,它可能会加快程序速度
- 它可能会减慢速度或提供错误的结果
分块可以提高速度的地方
读取较大的文件
这听起来很明显,但图书馆已经解决了。期待轻微或 none 改进。
获取块中的 CSV 记录
我不知道 csv.reader
或 csv.DictReader
的方法允许获取
直接记录大块。你必须自己做。这是可能的,我
建议使用 itertools.groupby
.
不要指望它本身的加速(它会减慢一点),但它是以后其他基于块的加速的先决条件。
将值块添加到集合中
代码(当前)将值一个一个地添加到集合中。如果你用块做 值(越多越好),它会更快,因为每个 python 调用都有 一些小开销。
计算平均值和标准差值
你可以利用 statistics
包,它可能有
优化代码(但它似乎是纯粹的 python)。
无论如何,因为您要分块处理数据,很简单
statistics.mean
对您不起作用,否则您必须汇总
结果以某种方式在一起(如果可能的话)。
如果你自己计算这个值,通过仔细编码你可以得到一些 加速,主要基于事实,你直接在一个块中获取值并且 不必在每个循环中逐个取消引用值。
分块结论
对我来说,分块优化似乎太复杂了,很难做到 预测,如果它带来任何价值。
Jan 的回答很好,但是如果你想加快速度,你可以在下面进行自己的研究:
执行的逐行分析:如果你不知道什么地方慢,你就无法进一步改进 (https://github.com/rkern/line_profiler)
由于您的大部分代码都是数值运算,您可能会在类型检查开销上浪费大量时间。这里有两条路径 - 用类型装饰代码并使用 Cython, or leave the code as it is and use Pypy - 两者都应该给你一个很好的提升。