5

在编写一种备份应用程序时,我对 Windows 上的文件复制性能进行了评估。

我有几个问题,我想知道你的意见。

谢谢!

卢卡斯。

问题:

  1. 为什么与 1 GiB 文件相比,复制 10 GiB 文件时的性能要慢得多?

  2. 为什么 shutil.copyfile 这么慢?

  3. 为什么 win32file.CopyFileEx 这么慢?这可能是因为标志 win32file.COPY_FILE_RESTARTABLE 吗?但是,它不接受 int 1000 作为标志 (COPY_FILE_NO_BUFFERING),建议将其用于大文件:http: //msdn.microsoft.com/en-us/library/aa363852%28VS.85%29.aspx

  4. 使用空的 ProgressRoutine 似乎对完全不使用 ProgressRoutine 没有影响。

  5. 是否有一种替代的、性能更好的方法来复制文件并获得进度更新?

1 GiB 和 10 GiB 文件的结果:

test_file_size             1082.1 MiB    10216.7 MiB

METHOD                      SPEED           SPEED
robocopy.exe                111.0 MiB/s     75.4 MiB/s
cmd.exe /c copy              95.5 MiB/s     60.5 MiB/s
shutil.copyfile              51.0 MiB/s     29.4 MiB/s
win32api.CopyFile           104.8 MiB/s     74.2 MiB/s
win32file.CopyFile          108.2 MiB/s     73.4 MiB/s
win32file.CopyFileEx A       14.0 MiB/s     13.8 MiB/s
win32file.CopyFileEx B       14.6 MiB/s     14.9 MiB/s

测试环境:

Python:
ActivePython 2.7.0.2 (ActiveState Software Inc.) based on
Python 2.7 (r27:82500, Aug 23 2010, 17:17:51) [MSC v.1500 64 bit (AMD64)] on win32

source = mounted network drive
source_os = Windows Server 2008 x64

destination = local drive
destination_os = Windows Server 2008 R2 x64

笔记:

'robocopy.exe' and 'cmd.exe /c copy' were run using subprocess.call()

win32file.CopyFileEx A(不使用 ProgressRoutine):

def Win32_CopyFileEx_NoProgress( ExistingFileName, NewFileName):
    win32file.CopyFileEx(
        ExistingFileName,                             # PyUNICODE           | File to be copied
        NewFileName,                                  # PyUNICODE           | Place to which it will be copied
        None,                                         # CopyProgressRoutine | A python function that receives progress updates, can be None
        Data = None,                                  # object              | An arbitrary object to be passed to the callback function
        Cancel = False,                               # boolean             | Pass True to cancel a restartable copy that was previously interrupted
        CopyFlags = win32file.COPY_FILE_RESTARTABLE,  # int                 | Combination of COPY_FILE_* flags
        Transaction = None                            # PyHANDLE            | Handle to a transaction as returned by win32transaction::CreateTransaction
        )

win32file.CopyFileEx B(使用空 ProgressRoutine):

def Win32_CopyFileEx( ExistingFileName, NewFileName):
    win32file.CopyFileEx(
        ExistingFileName,                             # PyUNICODE           | File to be copied
        NewFileName,                                  # PyUNICODE           | Place to which it will be copied
        Win32_CopyFileEx_ProgressRoutine,             # CopyProgressRoutine | A python function that receives progress updates, can be None
        Data = None,                                  # object              | An arbitrary object to be passed to the callback function
        Cancel = False,                               # boolean             | Pass True to cancel a restartable copy that was previously interrupted
        CopyFlags = win32file.COPY_FILE_RESTARTABLE,  # int                 | Combination of COPY_FILE_* flags
        Transaction = None                            # PyHANDLE            | Handle to a transaction as returned by win32transaction::CreateTransaction
        )

def Win32_CopyFileEx_ProgressRoutine(
    TotalFileSize,
    TotalBytesTransferred,
    StreamSize,
    StreamBytesTransferred,
    StreamNumber,
    CallbackReason,                         # CALLBACK_CHUNK_FINISHED or CALLBACK_STREAM_SWITCH
    SourceFile,
    DestinationFile,
    Data):                                  # Description
    return win32file.PROGRESS_CONTINUE      # return of any win32file.PROGRESS_* constant
4

4 回答 4

3

很有可能,因为您以不同的方式衡量完成时间。

我猜一个 1Gb 的文件很适合放在 ram 中。因此,操作系统可能只是在缓存它并告诉您的应用程序它已被复制,而其中大部分(可能全部)仍未在内核缓冲区中刷新。

但是,10G 文件不适合 ram,所以它必须在它说它完成之前写入(大部分)它。

如果你想要一个有意义的测量,

a)在每次运行之前清除文件系统缓冲区缓存 - 如果您的操作系统没有提供方便的方法,请重新启动(注意:Windows 没有提供方便的方法,我认为有一个系统内部工具可以做到这一点) . 在网络文件系统的情况下,也要清除服务器上的缓存。

b) 完成后,在测量完成时间之前将文件同步到光盘

然后我希望你会看到更一致的时间。

于 2010-11-25T17:00:58.850 回答
3

问题 3:

您误解了 Microsoft API 中的 COPY_FILE_NO_BUFFERING 标志。它不是 int 1000 而是十六进制 1000 (0x1000 => int value: 4096)。当您设置 CopyFlags = 4096 时,您将拥有 (?) Windows 环境中最快的复制例程。我在我的数据备份代码中使用相同的例程,它非常快并且每天传输 TB 大小的数据。

问题4:

没关系,因为它是一个回调。但总的来说,你不应该在里面放太多的代码,并保持它的干净和流畅。

问题 5:

根据我的经验,它是标准 Windows 环境中最快的复制程序。可能会有更快的自定义复制例程,但是当使用普通的 Windows API 时,找不到更好的方法。

于 2014-12-08T16:52:27.023 回答
2

回答你的问题2:

shutil.copyfile() 非常慢,因为默认情况下它使用 16Kbyte 的复制缓冲区。最终它以 shutil.copyfileobj() 结束,如下所示:

def copyfileobj(fsrc, fdst, length=16*1024):
    """copy data from file-like object fsrc to file-like object fdst"""
    while 1:
        buf = fsrc.read(length)
        if not buf:
            break
        fdst.write(buf)

在您的情况下,它在读取 16K 和写入 16K 之间反复进行。如果您要直接在 GB 文件上使用 copyfileobj(),但使用 128MB 的缓冲区,您会看到性能大大提高。

于 2013-04-05T11:34:50.857 回答
1

Lucas,我​​发现以下方法比 win32file.CopyFile 快 20%。

b = bytearray(8 * 1024 * 1024) 
# I find 8-16MB is the best for me, you try to can increase it 
with io.open(f_src, "rb") as in_file:
    with io.open(f_dest, "wb") as out_file:
        while True:
            numread = in_file.readinto(b)
            if not numread:
                break
            out_file.write(b)
            # status bar update here
shutil.copymode(f_src, f_dest)
于 2018-10-31T02:33:35.367 回答