涉及pyqt时文件复制太慢

File copy too slow when pyqt is involved

我正在使用此代码(非常简化的原始版本,但问题仍然存在)来复制文件:

def copyfileobj_example(source, dest, buffer_size=1024*1024):
    while 1:
        copy_buffer = source.read(buffer_size)
        if not copy_buffer:
            break
        dest.write(copy_buffer)

如果我在没有 pyqt 的情况下调用该函数,文件复制速度非常快,但是当我在一个简单的 pyqt 中调用它时 window,复制速度要慢三倍。

快速复制大量文件是应用程序的重点,我认为包括 gui 会稍微慢一点,但不会慢三倍!!并且 运行 使用线程或多进程的复制功能不会带来令人满意的改进。

这是原样吗?你能推荐我一些东西来解决这个性能问题吗?

编辑:There 是我实际复制代码的要点,运行 并且没有 PyQT

这可能是 GIL 的影响。在 UI 线程中使用 PyQt 运行,它 "steals" GIL 每次必须处理事件时。这意味着您上面的循环每次都会停止。当您 运行 在另一个线程中时,也会发生这种情况;锁是 global.

解决方法:

  1. 使用更大的缓冲区。 Python的C层不受GIL的影响,所以如果你复制的数据量较大,循环中的命令执行的频率就会降低。
  2. 执行外部命令进行复制(可能是另一个 Python 进程)。
  3. 使用 Qt 的 IO 类 复制文件,因为它们也不受 GIL 的影响(感谢 ekhumoro 的想法)。
  4. 用 C 编写一段代码来传输数据。
  5. 使用没有像 IronPython 或 Jython 这样的 GIL 的 Python 版本。

由于我无法使问题中的链接代码正常工作(它只是挂起并使用 100% CPU),我将 post 一个更合理的示例用于测试目的。

使用下面的测试用例,我在复制一个 400MB 的文件(运行三次)时得到以下输出:

$ python copy_test.py
2.9546546936035156
2.9658050537109375
$ python copy_test.py
3.226983070373535
3.192814826965332
$ python copy_test.py
2.935734748840332
2.8552770614624023

如您所见,没有显着差异。为清楚起见,这是在 Linux 上使用以下设置:

Python 3.5.0, Qt 5.5.0, PyQt 5.5

我在 Python 2.7/3.5、PyQt 4/5 的所有组合中得到了相似的结果。

这里是测试用例:

import sys
import time
import os

SRC_FILE = '/home/tmp/source/test/test.zip'
DEST_FILE = '/home/tmp/source/test/test-copy.zip'

def copy_file(src, dst=[], progress=None, only_new_file=True):
    size = 1024 * 1024
    with open(src, 'rb') as s, open(dst[0], 'wb') as d:
        while 1:
            copy_buffer = s.read(size)
            if not copy_buffer:
                break
            d.write(copy_buffer)

if __name__ == '__main__':

    initTime = time.time()
    copy_file(SRC_FILE, [DST_FILE], only_new_file=False)
    print (time.time() - initTime)

    time.sleep(5)

    from PyQt5.QtWidgets import QApplication, QWidget
#     from PyQt4.QtGui import QApplication, QWidget

    app = QApplication(sys.argv)

    w = QWidget()
    w.resize(250, 150)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()

    initTime = time.time()
    copy_file(SRC_FILE, [DST_FILE], only_new_file=False)
    print (time.time() - initTime)

    sys.exit(app.exec_())