优化I / O绑定的Win32应用程序

时间:2013-12-05 00:49:23

标签: c++ multithreading winapi io iocp

我正在尝试优化I / O绑定的C ++ Win32应用程序。它实际上做的是非常类似于递归文件夹并计算它找到的每个文件的加密哈希。 它是使用内存映射文件的单个线程应用程序,因此很容易想象它似乎没有使用太多CPU,因为大多数时候主线程处于休眠状态等待I / O完成。 我正在考虑几种解决方案,但我不确定,所以我希望得到你的意见。

  1. 我可以产生很多线程(有一个固定大小的工作池来保持内存使用率在一定的阈值以下),但老实说我不知道​​这是否可以使情况更好,每个线程都会像睡觉一样我当前实现中的主线程,加上调度程序会“浪费”大量的计算能力来切换上下文。
  2. 我在考虑I / O完成端口(单线程?多?),但这意味着放弃内存映射文件(我错了?)并使用标准文件操作。如果是这种情况,请您提供一些示例代码,说明如何使用IOCP来读取和详细说明给定的文件列表而不让阅读线程进入休眠状态?
  3. 任何其他想法/建议/等都会非常感激:)

    感谢。

2 个答案:

答案 0 :(得分:1)

我的经验表明内存映射不是特别快,所以这可能是我放弃的第一件事。

线程(显式地或通过IOCP)可能也不会有太大的好处,除非目标系统有很多磁盘驱动器,并且可以拆分,以便不同的线程在不同的物理驱动器上运行。

一旦放弃内存映射并进行显式I / O,您可能希望使用FILE_FLAG_NO_BUFFERING并读取相对较大的块(例如,一次几兆字节)。确实检查你的内存块的对齐要求 - 它们有点棘手(或者说“乏味”可能是描述它们的更好的词)。另请注意,这仅适用于磁盘扇区大小的倍数的读取,因此在典型情况下,您需要打开文件两次,一次使用FILE_FLAG_NO_BUFFERING读取大量数据,然后再次不使用该标志读取“文件的尾部。

虽然它只复制一个文件(而不是处理内容),但它可能是纯C而不是C ++,也许一些演示代码至少会有一些帮助:

int do_copy(char const *in, char const *out) {

    HANDLE infile;
    HANDLE outfile;
    char *buffer;
    DWORD read, written;
    DWORD junk=0;
    unsigned long little_tail;
    unsigned long big_tail;
    unsigned __int64 total_copied = 0;
    unsigned __int64 total_size = 0;
    BY_HANDLE_FILE_INFORMATION file_info;

#define size (1024 * 8192)

    buffer = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
    if ( NULL == buffer)
        return 0;

    infile = CreateFile(in, 
        GENERIC_READ, 
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS, 
        FILE_FLAG_NO_BUFFERING, 
        NULL);

    GetFileInformationByHandle(infile, &file_info);
    total_size = (unsigned __int64)file_info.nFileSizeHigh << 32 | (unsigned __int64)file_info.nFileSizeLow / 100;

    outfile = CreateFile(out, 
        GENERIC_WRITE, 
        FILE_SHARE_READ,
        NULL,
        CREATE_ALWAYS, 
        FILE_FLAG_NO_BUFFERING, 
        NULL);

    if ((infile == HNULL) || (outfile == HNULL))
        return 0;

    while (ReadFile(infile, buffer, size, &read, NULL) && read == size) {
        WriteFile(outfile, buffer, read, &written, NULL);
        total_copied += written;
        fprintf(stderr, "\rcopied: %lu %%", (unsigned long)(total_copied / total_size));
    }

    little_tail = read % 4096;
    big_tail = read - little_tail;

    WriteFile(outfile, buffer, big_tail, &written, NULL);

    CloseHandle(infile);
    CloseHandle(outfile);

    outfile = CreateFile(out, 
        GENERIC_WRITE, 
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_SEQUENTIAL_SCAN, 
        NULL);
    fprintf(stderr, "\rcopied: 100 %%\n");

    SetFilePointer(outfile, 0, &junk, FILE_END);
    WriteFile(outfile, buffer+big_tail, little_tail, &written, NULL);
    CloseHandle(outfile);
    VirtualFree(buffer, size, MEM_RELEASE);
    return 1;
}

答案 1 :(得分:1)

在对任何内容进行并行化之前,请先问问自己:增加的复杂性是否证明了获得的性能?为了以最小的努力回答这个问题,只需测试您已经拥有的最大读取吞吐量的百分比。也就是说,测试当前的读取吞吐量,然后测试最大吞吐量。不要将理论最大值用于此计算。然后,考虑一下即使最简单的方法获得最后几个百分比,也会引入多少复杂性以及引入多少可能的问题。

正如评论中已经提到的,这里最大的性能提升可能是通过流水线(即重叠计算和I / O)实现的。最简单的实现方法是使用异步读取。 This thread lists multiple ways to implement asnychronous file I/O in C++

如果您不需要便携性,只需使用Windows OVERLAPPED API即可。 Boost ASIO似乎不会使File I / O变得非常容易(但是)。我找不到任何好的例子。

请注意,根据您的系统配置,您必须启动多个线程才能使I / O带宽完全饱和(特别是如果该文件夹的文件实际驻留在多个磁盘上,这是可能的)。即使您只从一个设备读取,您也可以通过多个线程更好地(稍微)更好地减轻操作系统开销。