我怎样才能使这个 Python 文件扫描得更快?
How can I make this Python file scan faster?
编辑:Python 2.7.8
我有两个文件。 p_m 有几百条记录在第 2 列中包含可接受的值。p_t 有几千万条记录,我想确保第 14 列来自已经提到的可接受值集。因此,在第一个 while 循环中,我正在读取所有可接受的值,制作一个集合(用于重复数据删除),然后将该集合变成一个列表(我没有进行基准测试以查看集合是否比一个列表,实际上...)。我在第二个循环中将它减少到尽可能少的行,但我不知道它们是否是最快的几行(我使用了 [14] 索引两次,因为异常非常罕见以至于我没有'不想为变量赋值而烦恼)。目前扫描大约需要 40 分钟。关于如何改进它有什么想法吗?
def contentScan(p_m,p_t):
""" """
vcont=sets.Set()
i=0
h = open(p_m,"rb")
while(True):
line = h.readline()
if not line:
break
i += 1
vcont.add(line.split("|")[2])
h.close()
vcont = list(vcont)
vcont.sort()
i=0
h = open(p_t,"rb")
while(True):
line = h.readline()
if not line:
break
i += 1
if line.split("|")[14] not in vcont:
print "%s is not defined in the matrix." %line.split("|")[14]
return 1
h.close()
print "PASS All variable_content_id values exist in the matrix." %rem
return 0
是的,它根本不是最佳选择。 split
非常昂贵(创建新列表,创建 N 个字符串,将它们附加到列表)。扫描13s“|”,扫描14s“|” (从 13s pos)和 line[pos13 + 1:pos14 - 1]
.
很确定,通过这个小改动,您可以 运行 快 2-10 倍。要添加更多 - 您不能提取字符串,但可以循环遍历有效字符串,并且每个字符都从 pos13+1 个字符开始,同时字符匹配。如果你以“|”结尾对于其中一根琴弦 - 这是一个很好的琴弦。此外,按数据文件中的频率对有效字符串列表进行排序也会有所帮助。但不要在每一步创建包含数十个字符串的列表更为重要。
这是您的测试:
- 生成器(ts - 你可以调整它来为我们提供一些真实的数据)。
- 无操作(只是阅读)
- 原解
- 没有拆分解决方案
无法格式化代码,所以这是要点。
https://gist.github.com/iced/16fe907d843b71dd7b80
测试条件:具有 2 个内核和 1GB RAM 的 VBox 运行ning ubuntu 14.10 具有最新更新。每个变体执行 10 次,在每次 运行 之前重新启动 VBox 并抛出最低和最高 运行 时间。
结果:
no_op:25.025
原版:26.964
no_split: 25.102
原始 - no_op:1.939
no_split - no_op: 0.077
虽然在这种情况下,这种特殊的优化是无用的,因为大部分时间都花在了 IO 上。我无法找到使 IO 低于 70% 的测试设置。无论如何 - 拆分是昂贵的,在不需要时应该避免。
PS。是的,我知道在 good items
甚至 1K 的情况下,使用散列会更好(实际上,当散列计算比查找更快时更好 - 可能是 100 个元素),我的观点是拆分在这种情况下很昂贵。
检查数百项 set
中的成员资格 比检查同等 list
中的成员资格快 多 。但是,考虑到您惊人的 40 分钟 运行ning 时间,差异可能没有那么大的意义。例如:
ozone:~ alex$ python -mtimeit -s'a=list(range(300))' '150 in a'
100000 loops, best of 3: 3.56 usec per loop
ozone:~ alex$ python -mtimeit -s'a=set(range(300))' '150 in a'
10000000 loops, best of 3: 0.0789 usec per loop
因此,如果您正在检查 "tens of millions of times",使用该集合应该可以为您节省数十秒——总比没有好,但几乎无法衡量。
同样的考虑适用于其他非常可取的改进,例如转动循环结构:
h = open(p_t,"rb")
while(True):
line = h.readline()
if not line:
break
...
h.close()
变得更加时尚:
with open(p_t, 'rb') as h:
for line in h:
...
同样,这不会为您每次迭代节省一微秒——因此,比方说,超过 5000 万行,这还不到这 40 分钟中的一分钟。同上删除完全未使用的 i += 1
-- 它在那里没有任何意义,但采取这种方式不会有什么不同。
一个答案集中在 split
操作的成本上。这取决于您每条记录有多少个字段,但是,例如:
ozone:~ alex$ python -mtimeit -s'a="xyz|"*20' 'a.split("|")[14]'
1000000 loops, best of 3: 1.81 usec per loop
所以,再一次,这里的任何优化可能每次迭代最多可以为您节省一微秒——再一次,如果那样的话,又可以节省一分钟。
真的,这里的关键问题是为什么读取和检查例如 5000 万条记录应该花费 40 分钟——2400 秒——每行 48 微秒;毫无疑问,即使这里以及其他答案和评论中提到的所有优化,每行仍然超过 40 微秒。
因此,一旦您应用了所有优化(并确认代码仍然太慢),请尝试分析 程序——例如http://ymichael.com/2014/03/08/profiling-python-with-cprofile.html——找出所有时间的确切位置。
此外,为了确保它不只是某些特别慢的磁盘的 I/O,请对大循环 "commented out" 的内容部分执行 运行 - 只需阅读大文件,根本不对其进行任何处理或检查;这会告诉你什么是 "irreducible" I/O 开销(如果 I/O 是你花费的大部分时间的原因,那么你不能做太多改进,尽管改变开放open(thefile, 'rb', HUGE_BUFFER_SIZE)
可能会有所帮助)并且可能需要考虑改进硬件设置——对磁盘进行碎片整理,使用本地文件系统而不是远程文件系统,等等...
编辑:制作更多 pythony。
编辑 2:使用 set builtin 而不是 dict。
根据评论,我决定尝试使用 dict 而不是列表来存储可接受的值,我将根据这些值检查大文件(我确实密切关注 .split 但没有改变它)。基于仅将列表更改为字典,我发现执行时间立即有了巨大的改进。
对一百万行文件使用 timeit
和 运行 5 次迭代,基于列表的检查器得到 884.2 秒,基于字典的检查器得到 25.4 秒!所以就像更改 2 或 3 行的 34 倍改进一样。
感谢大家的启发!这是我找到的解决方案:
def contentScan(p_m,p_t):
""" """
vcont=set()
with open(p_m,'rb') as h:
for line in h:
vcont.add(line.split("|")[2])
with open(p_t,"rb") as h:
for line in h:
if line.split("|")[14] not in vcont:
print "%s is not defined in the matrix." %line.split("|")[14]
return 1
print "PASS All variable_content_id values exist in the matrix."
return 0
列表查找是问题所在(正如您正确注意到的那样)。搜索列表具有 O(n) 时间复杂度,其中 n 是存储在列表中的项目数。另一方面,在哈希表中查找值(这就是 python 字典的实际含义)具有 O(1) 的复杂性。由于列表中有数百个项目,因此列表查找的成本比字典查找高大约两个数量级。这与您在用字典替换列表时看到的 34 倍改进一致。
要进一步将执行时间减少 5-10 倍,您可以使用 Python JIT。我个人喜欢 Pypy http://pypy.org/features.html 。您不需要修改脚本,只需安装 pypy 和 运行:
pypy [your_script.py]
编辑:Python 2.7.8
我有两个文件。 p_m 有几百条记录在第 2 列中包含可接受的值。p_t 有几千万条记录,我想确保第 14 列来自已经提到的可接受值集。因此,在第一个 while 循环中,我正在读取所有可接受的值,制作一个集合(用于重复数据删除),然后将该集合变成一个列表(我没有进行基准测试以查看集合是否比一个列表,实际上...)。我在第二个循环中将它减少到尽可能少的行,但我不知道它们是否是最快的几行(我使用了 [14] 索引两次,因为异常非常罕见以至于我没有'不想为变量赋值而烦恼)。目前扫描大约需要 40 分钟。关于如何改进它有什么想法吗?
def contentScan(p_m,p_t):
""" """
vcont=sets.Set()
i=0
h = open(p_m,"rb")
while(True):
line = h.readline()
if not line:
break
i += 1
vcont.add(line.split("|")[2])
h.close()
vcont = list(vcont)
vcont.sort()
i=0
h = open(p_t,"rb")
while(True):
line = h.readline()
if not line:
break
i += 1
if line.split("|")[14] not in vcont:
print "%s is not defined in the matrix." %line.split("|")[14]
return 1
h.close()
print "PASS All variable_content_id values exist in the matrix." %rem
return 0
是的,它根本不是最佳选择。 split
非常昂贵(创建新列表,创建 N 个字符串,将它们附加到列表)。扫描13s“|”,扫描14s“|” (从 13s pos)和 line[pos13 + 1:pos14 - 1]
.
很确定,通过这个小改动,您可以 运行 快 2-10 倍。要添加更多 - 您不能提取字符串,但可以循环遍历有效字符串,并且每个字符都从 pos13+1 个字符开始,同时字符匹配。如果你以“|”结尾对于其中一根琴弦 - 这是一个很好的琴弦。此外,按数据文件中的频率对有效字符串列表进行排序也会有所帮助。但不要在每一步创建包含数十个字符串的列表更为重要。
这是您的测试:
- 生成器(ts - 你可以调整它来为我们提供一些真实的数据)。
- 无操作(只是阅读)
- 原解
- 没有拆分解决方案
无法格式化代码,所以这是要点。
https://gist.github.com/iced/16fe907d843b71dd7b80
测试条件:具有 2 个内核和 1GB RAM 的 VBox 运行ning ubuntu 14.10 具有最新更新。每个变体执行 10 次,在每次 运行 之前重新启动 VBox 并抛出最低和最高 运行 时间。
结果: no_op:25.025 原版:26.964 no_split: 25.102
原始 - no_op:1.939 no_split - no_op: 0.077
虽然在这种情况下,这种特殊的优化是无用的,因为大部分时间都花在了 IO 上。我无法找到使 IO 低于 70% 的测试设置。无论如何 - 拆分是昂贵的,在不需要时应该避免。
PS。是的,我知道在 good items
甚至 1K 的情况下,使用散列会更好(实际上,当散列计算比查找更快时更好 - 可能是 100 个元素),我的观点是拆分在这种情况下很昂贵。
检查数百项 set
中的成员资格 比检查同等 list
中的成员资格快 多 。但是,考虑到您惊人的 40 分钟 运行ning 时间,差异可能没有那么大的意义。例如:
ozone:~ alex$ python -mtimeit -s'a=list(range(300))' '150 in a'
100000 loops, best of 3: 3.56 usec per loop
ozone:~ alex$ python -mtimeit -s'a=set(range(300))' '150 in a'
10000000 loops, best of 3: 0.0789 usec per loop
因此,如果您正在检查 "tens of millions of times",使用该集合应该可以为您节省数十秒——总比没有好,但几乎无法衡量。
同样的考虑适用于其他非常可取的改进,例如转动循环结构:
h = open(p_t,"rb")
while(True):
line = h.readline()
if not line:
break
...
h.close()
变得更加时尚:
with open(p_t, 'rb') as h:
for line in h:
...
同样,这不会为您每次迭代节省一微秒——因此,比方说,超过 5000 万行,这还不到这 40 分钟中的一分钟。同上删除完全未使用的 i += 1
-- 它在那里没有任何意义,但采取这种方式不会有什么不同。
一个答案集中在 split
操作的成本上。这取决于您每条记录有多少个字段,但是,例如:
ozone:~ alex$ python -mtimeit -s'a="xyz|"*20' 'a.split("|")[14]'
1000000 loops, best of 3: 1.81 usec per loop
所以,再一次,这里的任何优化可能每次迭代最多可以为您节省一微秒——再一次,如果那样的话,又可以节省一分钟。
真的,这里的关键问题是为什么读取和检查例如 5000 万条记录应该花费 40 分钟——2400 秒——每行 48 微秒;毫无疑问,即使这里以及其他答案和评论中提到的所有优化,每行仍然超过 40 微秒。
因此,一旦您应用了所有优化(并确认代码仍然太慢),请尝试分析 程序——例如http://ymichael.com/2014/03/08/profiling-python-with-cprofile.html——找出所有时间的确切位置。
此外,为了确保它不只是某些特别慢的磁盘的 I/O,请对大循环 "commented out" 的内容部分执行 运行 - 只需阅读大文件,根本不对其进行任何处理或检查;这会告诉你什么是 "irreducible" I/O 开销(如果 I/O 是你花费的大部分时间的原因,那么你不能做太多改进,尽管改变开放open(thefile, 'rb', HUGE_BUFFER_SIZE)
可能会有所帮助)并且可能需要考虑改进硬件设置——对磁盘进行碎片整理,使用本地文件系统而不是远程文件系统,等等...
编辑:制作更多 pythony。 编辑 2:使用 set builtin 而不是 dict。
根据评论,我决定尝试使用 dict 而不是列表来存储可接受的值,我将根据这些值检查大文件(我确实密切关注 .split 但没有改变它)。基于仅将列表更改为字典,我发现执行时间立即有了巨大的改进。
对一百万行文件使用 timeit
和 运行 5 次迭代,基于列表的检查器得到 884.2 秒,基于字典的检查器得到 25.4 秒!所以就像更改 2 或 3 行的 34 倍改进一样。
感谢大家的启发!这是我找到的解决方案:
def contentScan(p_m,p_t):
""" """
vcont=set()
with open(p_m,'rb') as h:
for line in h:
vcont.add(line.split("|")[2])
with open(p_t,"rb") as h:
for line in h:
if line.split("|")[14] not in vcont:
print "%s is not defined in the matrix." %line.split("|")[14]
return 1
print "PASS All variable_content_id values exist in the matrix."
return 0
列表查找是问题所在(正如您正确注意到的那样)。搜索列表具有 O(n) 时间复杂度,其中 n 是存储在列表中的项目数。另一方面,在哈希表中查找值(这就是 python 字典的实际含义)具有 O(1) 的复杂性。由于列表中有数百个项目,因此列表查找的成本比字典查找高大约两个数量级。这与您在用字典替换列表时看到的 34 倍改进一致。
要进一步将执行时间减少 5-10 倍,您可以使用 Python JIT。我个人喜欢 Pypy http://pypy.org/features.html 。您不需要修改脚本,只需安装 pypy 和 运行:
pypy [your_script.py]