我怎样才能使这个 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 个字符开始,同时字符匹配。如果你以“|”结尾对于其中一根琴弦 - 这是一个很好的琴弦。此外,按数据文件中的频率对有效字符串列表进行排序也会有所帮助。但不要在每一步创建包含数十个字符串的列表更为重要。

这是您的测试:

  1. 生成器(ts - 你可以调整它来为我们提供一些真实的数据)。
  2. 无操作(只是阅读)
  3. 原解
  4. 没有拆分解决方案

无法格式化代码,所以这是要点。

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]