【问题标题】:Python multithreading - writing to file is 10x slowerPython 多线程 - 写入文件慢 10 倍
【发布时间】:2015-09-18 18:23:47
【问题描述】:

我一直在尝试将包含多行(270 亿行)的大文件转换为 JSON。 Google Compute 建议我利用多线程来缩短写入时间。我已经从这里转换了我的代码:

import json
import progressbar
f = open('output.txt', 'r')
r = open('json.txt', 'w')
import math
num_permutations = (math.factorial(124)/math.factorial((124-5)))
main_bar = progressbar.ProgressBar(maxval=num_permutations, \
widgets=[progressbar.Bar('=', '[', ']'), ' ', progressbar.Percentage(), progressbar.AdaptiveETA()])
main_bar.start()
m = 0
for lines in f:
        x = lines[:-1].split(' ')
        x = json.dumps(x)
        x += '\n'
        r.write(x)
        m += 1
        main_bar.update(m)

到这里:

import json
import progressbar
from Queue import Queue
import threading
q = Queue(maxsize=5)
def worker():
        while True:
                task = q.get()
                r.write(task)
                q.task_done()
for i in range(4):
        t = threading.Thread(target=worker)
        t.daemon = True
        t.start()
f = open('output.txt', 'r')
r = open('teams.txt', 'w')
import math
num_permutations = (math.factorial(124)/math.factorial((124-5)))
main_bar = progressbar.ProgressBar(maxval=num_permutations, \
widgets=[progressbar.Bar('=', '[', ']'), ' ', progressbar.Percentage(), progressbar.AdaptiveETA()])
main_bar.start()
m = 0
for lines in f:
        x = lines[:-1].split(' ')
        x = json.dumps(x)
        x += '\n'
        q.put(x)
        m += 1
        main_bar.update(m)

我几乎直接从模块手册中复制了队列编码。

以前,整个脚本需要 2 天时间。现在是20天!我不太清楚为什么,谁能给我解释一下?

编辑:这可能被认为是 Python 全局解释器锁定 (GIL) 问题,但是,我认为并非如此 - 它不是计算密集型的,并且是 IO 瓶颈问题,来自线程文档:

如果您希望您的应用程序更好地利用计算 多核机资源,建议使用 多处理。但是,线程仍然是一个合适的模型,如果 您想同时运行多个 I/O 密集型任务。

我对此的理解是有限的,但我相信这是后者,即。一个 IO 绑定任务。这是我最初想使用多线程时的最初想法:计算被 IO 调用阻塞,可以将其放到单独的线程中以允许计算功能继续。

进一步编辑: 也许事实是我从 INPUT 获得了一个 IO 块,这就是它减慢速度的原因。关于如何有效地将“for”循环发送到单独的线程的任何想法?谢谢!

【问题讨论】:

  • 您是否在数据集的一小部分上尝试过这个?你在什么样的环境下工作?
  • 您只是增加了多个编写者共享对单个输出文件的访问权限的复杂性。如果每个线程都写入一个单独的文件,您可能会看到改进,尽管您最终可能仍希望连接各个文件。
  • 无论你做什么,如此精细的分割可能会让通信成为应用程序的最大负担。为什么不一次发送几千行的块?为什么不在一次操作中写入几千行文件呢?
  • FWIW factorial(a)/factorial(a-b) = reduce(operator.mul, range(a, a-b, -1))
  • “Python(至少是 CPython)有一个全局解释器锁,可以防止多个线程一次访问内存。” 毫无意义。 GIL 在 I/O 期间释放,C 扩展模块如 numpy、regex、lxml 也可能在 C 中长时间计算期间释放 GIL。GIL 是关于 Python 代码的;这与记忆无关。虽然我不认为多线程会以某种方式使硬盘更难工作(如果它们是这里的瓶颈的话)。

标签: python multithreading


【解决方案1】:

如果我们删除progressbar 代码,那么您的代码相当于:

#!/usr/bin/env python2
import json
import sys

for line in sys.stdin:        
    json.dump(line.split(), sys.stdout) # split on any whitespace
    print

为了提高时间性能,你应该先测量它——取一个小的输入文件,这样执行时间不超过一分钟,然后运行:

$ /usr/bin/time ./your-script < output.txt > json.txt

我不知道您为什么认为将二进制 blob 从多个线程写入同一个文件应该更快。

这里的性能瓶颈有哪些候选:

  • 循环开销(如果线路很小且磁盘速度很快)。将代码放入函数中(由于将全局查找替换为本地查找,可能会提高 CPython 的性能)
  • json.dump()(不太可能,但无论如何都要测量)——使用诸如 ensure_ascii=False 之类的参数并测量结果。尝试其他 json 模块,不同的 Python 实现。
  • 磁盘 I/O -- 将结果文件放在不同的物理磁盘上(运行类似iotopcsysdig 以查看进程如何消耗资源)
  • unicode 字节转换,EOL 转换 -- 以二进制模式打开文件,将 json 文本编码为字节

【讨论】:

    【解决方案2】:

    如果你想加快速度,就不要使用 Python ——这个任务很简单,可以用 Unix 过滤器来处理,例如:

    sed 's/ /", "/g; s/^/["/; s/$/"]/' output.txt > json.txt
    

    有关其工作原理的说明,请参见此处:https://stackoverflow.com/a/14427404/4323

    如果您关心速度,那么使用 Python 的问题在于,您基本上是一次从输入文件中读取一行。现在,您可以通过多种奇特的方式进行阅读(分而治之),但如果您只是想加快阅读速度,上述方法应该可以解决问题。

    如果你想要一个进度条,请使用 pv(来自许多 Linux 系统上的包 moreutils),我认为这样可以:

    pv output.txt | sed 's/ /", "/g; s/^/["/; s/$/"]/' > json.txt
    

    【讨论】:

    • 注意:如果output.txt包含Unicode字符,它可能会产生无效的json.txt
    • 它确实有unicode字符,我们在看什么样的无效?源文件(output.txt)也大约 500GB,这有什么问题吗?
    • 500 GB 对于 STream 编辑器 sed 来说不是问题,因为它会忘记之前的行。至于Unicode,试试吧,它可能会按原样工作(虽然我不保证!)。当然,源文件中的引号也可能会造成混乱,但您的 Python 代码也是如此。
    • @JohnZwinck:错误,Python 代码转义了引号,以及其他无法在 json 字符串中逐字出现的 Unicode 字符。你的命令产生错误结果的速度有多快并不重要。
    • @J.F.Sebastian:哦,我现在明白了,感谢您指出。至于 Unicode,我仍然认为我的解决方案可能有效,但正如我所说,我还没有针对这种情况进行测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多