关于python:多线程文件复制比多核CPU上的单个线程慢得多

Multithreaded file copy is far slower than a single thread on a multicore CPU

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

环境:

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

方法:

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

  • 连续复制需要150-160秒
  • 线程复制需要230秒以上

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    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情况。我在我的机器上做了一个类似的测试,从一个非常快的网络服务器复制到它自己上,我发现仅仅使用上面的代码(4个线程),速度几乎提高了1:1。我的测试是复制4137个文件,总计16.5g:

1
2
3
4
5
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多进程/多线程加速文件复制。我还没有机会尝试这个代码,但Gevent可能更快。

  • 斯宾塞


I assume this is more a I/O bound task, multithread should help the operation speed, anything wrong with my approach?!

对.

  • 标点符号太多。只有一个。"?"是适当的。

  • 你的假设是错误的。多线程帮助CPU绑定(有时)。它无法帮助I/O绑定。从未。

  • 一个进程中的所有线程都必须等待一个线程进行I/O。

    or coroutine to do the job?!

    不。

    如果你想做大量的I/O,你需要很多的过程。

    如果您要复制1000个文件,那么您需要许多许多进程。每个进程复制一些文件。


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

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

    结论:如果您希望获得最大的性能,可以使用单线程,但是使用异步API可以让操作系统更好地调度读取请求。


    作为旁白,我只是想补充一下,上面的代码有点错误。你应该打电话shutil.copy(文件名,目标路径)之后的filequeue.task_done()。否则将不会复制最后一个文件:)