繁体   English   中英

如何基准测试在Windows中复制文件的最快方法(内存映射与文件I / O)

[英]How to benchmark the fastest method to copy a file in Windows (memory-mapped vs. file I/O)

(出于这个问题的目的,我忽略了文件复制API,如CopyFile等)

我试图回答这个问题:如果我需要复制许多大文件,哪种方法最快?

我可以想到复制文件的四种基本方法:

  1. ReadFile + WriteFile
  2. MapViewOfFile将源存入内存,并将WriteFile缓冲区传送到目标
  3. MapViewOfFile将目标放入内存,并将ReadFile源放入缓冲区
  4. MapViewOfFile将文件和memcpy从一个文件转移到另一个文件

此外,在每种情况下,我还可以设置一些选项,例如FILE_FLAG_NO_BUFFERINGSEC_LARGE_PAGE

但是,我不知道如何正确地对此进行基准测试。 我写了以下代码:

#include <stdio.h>
#include <time.h>
#include <tchar.h>
#include <Windows.h>

void MyCopyFile(HANDLE source, HANDLE sink, bool mapsource, bool mapsink)
{
    LARGE_INTEGER size = { 0 };
    GetFileSizeEx(source, &size);
    HANDLE msource = mapsource ? CreateFileMapping(source, NULL, PAGE_READONLY, 0, 0, NULL) : NULL;
    HANDLE msink = mapsink ? CreateFileMapping(sink, NULL, PAGE_READWRITE, size.HighPart, size.LowPart, NULL) : NULL;
    void const *const psource = mapsource ? MapViewOfFile(msource, FILE_MAP_READ, 0, 0, size.QuadPart) : NULL;
    void *const psink = mapsink ? MapViewOfFile(msink, FILE_MAP_WRITE, 0, 0, size.QuadPart) : NULL;
    clock_t const start = clock();
    unsigned long nw = 0;
    if (mapsource)
    {
        if (mapsink)
        {
            memcpy(psink, psource, size.QuadPart);
            nw = size.QuadPart;
        }
        else
        { WriteFile(sink, psource, size.QuadPart, &nw, NULL); }
    }
    else
    {
        if (mapsink)
        { ReadFile(source, psink, size.QuadPart, &nw, NULL); }
        else
        {
            void *const buf = malloc(size.QuadPart);
            if (!ReadFile(source, buf, size.QuadPart, &nw, NULL)) { fprintf(stderr, "Error reading from file: %u\n", GetLastError()); }
            if (!WriteFile(sink, buf, size.QuadPart, &nw, NULL)) { fprintf(stderr, "Error writing to file: %u\n", GetLastError()); }
            free(buf);
        }
    }
    FlushViewOfFile(psink, size.QuadPart);
    clock_t const end = clock();
    if (mapsource) { UnmapViewOfFile(psource); }
    if (mapsink) { UnmapViewOfFile(psink); }
    if (mapsource) { CloseHandle(msource); }
    if (mapsink) { CloseHandle(msink); }
    if (nw) { fprintf(stderr, "(%d, %d): %u MiB/s\n", mapsource, mapsink, (unsigned int)(size.QuadPart * CLOCKS_PER_SEC / (((long long)(end - start) << 20) + 1))); }
}
int main()
{
    // Request permission to extend file without zeroing, for faster performance
    {
        enum TokenPrivilege { SeManageVolumePrivilege = 28 };
        typedef NTSTATUS NTAPI PRtlAdjustPrivilege(IN TokenPrivilege Privilege, IN BOOLEAN Enable, IN BOOLEAN Client, OUT PBOOLEAN WasEnabled);
        static PRtlAdjustPrivilege &RtlAdjustPrivilege = *(PRtlAdjustPrivilege *)(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), _CRT_STRINGIZE(RtlAdjustPrivilege)));
        BOOLEAN old; RtlAdjustPrivilege(SeManageVolumePrivilege, TRUE, FALSE, &old);
    }
    for (int i = 0;; i++)
    {
        HANDLE source = CreateFile(_T("TempSource.bin"), FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
        HANDLE sink = CreateFile(_T("TempSink.bin"), FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
        LARGE_INTEGER size; size.QuadPart = 1 << 26;
        LARGE_INTEGER zero = { 0 };
        SetFilePointerEx(source, size, &size, FILE_BEGIN);
        SetEndOfFile(source);
        SetFileValidData(source, size.QuadPart);
        SetFilePointerEx(source, zero, &zero, FILE_BEGIN);
        SetFilePointerEx(sink, zero, &zero, FILE_BEGIN);
        MyCopyFile(source, sink, i % 2 != 0, i / 2 % 2 != 0);
        FlushFileBuffers(source);
        FlushFileBuffers(sink);
        if ((i % 4) + 1 == 4) { fprintf(stderr, "\n"); }
        CloseHandle(source);
        CloseHandle(sink);
    }
}

不幸的是,我的代码在第一次迭代时给出的结果比以下迭代的结果大得多,所以我很难弄清楚如何对此操作进行基准测试。

哪种方法应该最快,以及如何正确地对我的系统进行基准测试以确认这一点?

我认为这取决于您的性能跟踪需要多严重。 您可以像计算自己需要多长时间一样来计时,或者您可以考虑使用ETW ETW是Windows中所有性能事物的跟踪方式。 此外,ETW是Windows如何执行高性能事件和跟踪。 如果您正在进行系统性能跟踪,则表示您正在使用ETW。 此外,一旦您的组件与ETW正确连接,您就可以从用户模式组件一直跟踪整个系统的性能,一直到内核模式。 我不是真正的VS用户,但我认为有一些工具可以自动为您的组件添加分析。 我使用过像F1和TDD这样的工具......在某些时候可能已经集成到了VS.

此外,还有一些疯狂的工具可用于深入研究性能跟踪的结果ETL文件。 比如,您是否对堆碎片或给定堆栈的CPU时间感兴趣? 通过适当的性能跟踪,您可以基本计算出您(或系统)性能的任何维度。

其中一个基本概念是活动ID。 它是在线程本地存储上设置的GUID。 它是将事件/场景拼接在一起的东西。

首先尝试捕获性能跟踪。 弄清楚如何解码ETL文件。 开始向您的代码添加活动ID。 解码,ETL文件并开始测量性能或您的方案。

无论如何,这是系统性能跟踪的严重程度。 希望这是一个有用的起点。

如果您不需要那么严肃,那么只需在代码中使用计时器。

void GetSetActivityId(
    _Out_ GUID *pActivityId) 
{
GUID activityId = {0};
GUID guidNull = {0};

TRACE_FUNCTION_ENTRY(LEVEL_INFO);

// check to see if there is already an activity id on the thread
(void)EventActivityIdControl(EVENT_ACTIVITY_CTRL_GET_ID, &activityId);

if (RtlCompareMemory(&guidNull, &activityId, sizeof(GUID)) == sizeof(GUID)) {
    // we will create and set an activity id because we didn't get one from the thread
    if (EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &activityId) == ERROR_SUCCESS) {
        (void)EventActivityIdControl(EVENT_ACTIVITY_CTRL_SET_ID, &activityId);
    }
}

TRACE_FUNCTION_EXIT(LEVEL_COND);

*pActivityId = activityId;
}  // GetSetActivityId

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM