在Windows x64上使用Python x64位拷贝文件的性能评估/问题。

5

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

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

谢谢!

卢卡斯。

问题:

  1. 为什么复制10 GiB文件的性能比复制1 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):


此函数用于在Windows系统中复制文件,它可以在不使用 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

1
@t0mt0m72的新回答比其他人更详细地解释了这个问题。 - Elemental
4个回答

3
很有可能是因为您正在以不同的方式测量完成时间。
我猜测1GB的文件可以轻松地放入内存中。因此,操作系统可能只是将其缓存并告诉应用程序已复制,而大部分(或许全部)仍未刷新到内核缓冲区。
然而,10GB的文件无法放入内存中,所以在它说完成之前必须写入(大部分)。
如果您想要有意义的测量结果,
a) 在每次运行之前清除文件系统缓存——如果您的操作系统没有提供方便的方法来执行此操作,请重新启动(注意:Windows没有提供方便的方法,但我认为有一个系统内部工具可以做到)。对于网络文件系统,请在服务器上清除缓存。
b) 在测量完成时间之前,在完成后将文件同步到磁盘。
然后我希望您会看到更一致的时间。

3

第三个问题:

你误解了Microsoft API中COPY_FILE_NO_BUFFERING标志的含义。它不是整数1000,而是十六进制数0x1000(int值为4096)。当你将CopyFlags设置为4096时,在Windows环境中将拥有最快的复制例程。我在我的数据备份代码中使用了同样的例程,它非常快速,并且每天传输以太字节大小的数据。

第四个问题:

由于这是一个回调函数,所以具体怎么写没有太大关系。但总的来说,你不应该在其中放置过多的代码,保持简洁流畅最好。

第五个问题:

根据我的经验,在标准的Windows环境中,这是可能的最快的复制例程。可能会有更快的定制复制例程,但在使用纯Windows API时找不到更好的。


相当久远的事情了,但我想我使用了win32file.COPY_FILE_NO_BUFFERING而不是int 1000,所以win32file可能存在缺陷。 我还没有时间测试你的建议。 一个问题:如果COPY_FILE_NO_BUFFERING意味着不使用缓冲区或缓存,为什么它应该很快呢?由于总有后台服务或用户访问资源,例如网络带宽、源或目标磁盘访问等,总会出现延迟。有一个大缓冲区似乎更合理,而不是没有缓冲区! - lucas0x7B

2
为了回答您的第二个问题:
shutil.copyfile()的速度很慢,因为默认情况下它使用16KB的复制缓冲区。最终它会进入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的缓冲区,则会看到显着提高的性能。


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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接