【问题标题】:Multithreaded file copy is far slower than a single thread on a multicore CPU多线程文件复制比多核 CPU 上的单线程慢得多
【发布时间】:2012-01-24 23:14:33
【问题描述】:

我正在尝试用 Python 编写一个多线程程序来加速复制(少于 1000 个).csv 文件。多线程代码的运行速度甚至比顺序方法还要慢。我用profile.py 为代码计时。我确定我一定做错了什么,但我不确定是什么。

环境:

  • 四核 CPU。
  • 2 个硬盘驱动器,其中一个包含源文件。另一个是目的地。
  • 1000 个 csv 文件,大小从几 KB 到 10 MB 不等。

方法:

我把所有的文件路径放在一个Queue中,并创建4-8个工作线程从队列中拉取文件路径并复制指定的文件。在任何情况下,多线程代码都不会更快:

  • 连续复制需要 150-160 秒
  • 线程复制需要超过 230 秒

我假设这是一个 I/O 绑定任务,所以多线程应该有助于提高操作速度。

守则:

    import Queue
    import threading
    import cStringIO 
    import os
    import shutil
    import timeit  # time the code exec with gc disable
    import glob    # file wildcards list, glob.glob('*.py')
    import profile # 

    fileQueue = Queue.Queue() # global
    srcPath  = 'C:\\temp'
    destPath = 'D:\\temp'
    tcnt = 0
    ttotal = 0

    def CopyWorker():
        while True:
            fileName = fileQueue.get()
            fileQueue.task_done()
            shutil.copy(fileName, destPath)
            #tcnt += 1
            print 'copied: ', tcnt, ' of ', ttotal

    def threadWorkerCopy(fileNameList):
        print 'threadWorkerCopy: ', len(fileNameList)
        ttotal = len(fileNameList)
        for i in range(4):
            t = threading.Thread(target=CopyWorker)
            t.daemon = True
            t.start()
        for fileName in fileNameList:
            fileQueue.put(fileName)
        fileQueue.join()

    def sequentialCopy(fileNameList):
        #around 160.446 seconds, 152 seconds
        print 'sequentialCopy: ', len(fileNameList)
        cnt = 0
        ctotal = len(fileNameList)
        for fileName in fileNameList:
            shutil.copy(fileName, destPath)
            cnt += 1
            print 'copied: ', cnt, ' of ', ctotal

    def main():
        print 'this is main method'
        fileCount = 0
        fileList = glob.glob(srcPath + '\\' + '*.csv')
        #sequentialCopy(fileList)
        threadWorkerCopy(fileList)

    if __name__ == '__main__':
        profile.run('main()')

【问题讨论】:

  • 您说“我认为它受 I/O 限制”然后说“多线程应该有助于提高操作速度”。你看错了。 I/O bound 意味着它受 I/O 约束,而不是 CPU。如果它受 CPU 限制,多线程会有所帮助。
  • 当然它更慢 - 您是否为每个要生成的线程设置了一组硬盘驱动器头?
  • robocopy C:\temp D:\temp *.csv比较
  • 谢谢大家,多多了解真好!

标签: python multithreading file copy queue


【解决方案1】:

当然它更慢。硬盘驱动器必须不断地在文件之间寻找。您认为多线程会使这项任务更快的信念是完全没有道理的。限制速度是您可以从磁盘读取数据或将数据写入磁盘的速度,从一个文件到另一个文件的每次寻道都会浪费本可用于传输数据的时间。

【讨论】:

    【解决方案2】:

    我想我可以验证这是磁盘 I/O 情况。我在我的机器上做了一个类似的测试,从一个非常快的网络服务器复制回它自己,我看到使用上面的代码(4 个线程)几乎 1:1 的速度增加。我的测试是复制4137个文件共16.5G:

    Sequential copy was 572.033 seconds.
    Threaded (4) copy was 180.093 seconds.
    Threaded (10) copy was 110.155
    Threaded (20) copy was 86.745
    Threaded (40) copy was 87.761
    

    正如您所看到的,随着线程数越来越多,会有一点“衰减”,但是在 4 个线程时,我的速度有了巨大的提升。我在一台速度非常快的计算机上,网络连接速度非常快,所以我认为我可以安全地假设您正在达到 I/O 限制。

    也就是说,看看我在这里得到的共鸣:Python multiprocess/multithreading to speed up file copying。我还没有机会尝试这段代码,但 gevent 可能会更快。

    • 斯宾塞

    【讨论】:

    • 我还可以在 1Gig 快速以太网网络上通过将 500Mb 数据从一个文件管理器位置复制到另一个位置来验证这一点。顺序复制耗时 45 秒,而使用上述代码的基于 16 个工作线程的副本耗时 17.4 秒。基于线程的副本显示 I/O 绑定的性能提升。
    【解决方案3】:

    我认为这更像是一个 I/O 绑定任务,多线程应该有助于提高运行速度,我的方法有什么问题吗?!

    是的。

    1. 标点符号过多。只有一个。 “?”是合适的。

    2. 你的假设是错误的。多线程有助于 CPU 绑定(有时)。它永远无法帮助 I/O 绑定。从不。

    一个进程中的所有线程必须等待,而一个线程执行 I/O。

    还是协程来完成这项工作?!

    没有。

    如果你想做很多 I/O,你需要很多进程。

    如果您要复制 1000 个文件,则需要很多很多的进程。每个进程都会复制一些文件。

    【讨论】:

    • 技术上的多线程可以帮助一些 I/O 绑定实例。如果 I/O 延迟较高(例如 Web 请求),则执行多个线程会有所帮助。但总的来说,我同意你的看法。
    【解决方案4】:

    顺便说一句,我只是想补充一下,上面的代码有点错误。你应该打电话 fileQueue.task_done() AFTER shutil.copy(fileName, destPath) .. 否则最后的文件将不会被复制:)

    【讨论】:

      【解决方案5】:

      存在cpu bounded 应用程序和i/o bounded 应用程序,通常当它的顺序版本是cpu bounded 时,您可以从多线程应用程序中获得几乎线性的好处。 但是,当您受到 i/o 限制时,您将一无所获,许多操作系统可以向您显示 CPU 的“忙时间百分比”和“磁盘忙时间百分比”,这样您就可以知道您的情况。

      但是,由于通常顺序代码不是异步的,您结束获取一个文件,然后等待该文件副本,然后是下一个文件。这样您就可以避免操作系统拥有文件列表并根据表面磁盘位置对读取请求进行优先级排序。

      结论:如果您寻求最高性能,请选择单线程,但使用异步 API 以允许操作系统更好地调度读取请求。

      【讨论】:

        【解决方案6】:

        多线程 I/O 方法仅在通过高延迟 TCP 连接进行传输时才有优势,其中 TCP 窗口化将限制单个 TCP 连接的吞吐量。在源和目标之间创建多个 TCP 连接,并在这些连接中交错文件(这需要多线程),可以比标准的 FTP 或 NFS 副本执行得更好,NetApp 的 XCP 之类的程序可以做到这一点。如果您的延迟很低或副本是本地的,那么您可以寻求实现的唯一效率就是绕过文件系统从瓶颈情况(即数百万个文件等)中所做的任何事情,而答案不是你在做什么。

        【讨论】: