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

pes8fvy9  于 2023-03-25  发布在  其他
关注(0)|答案(5)|浏览(179)

我试图优化一个I/O绑定的C++ Win32应用程序。它实际上做的是非常类似于递归一个文件夹并计算它找到的每个文件的加密哈希。它是一个使用内存Map文件的单线程应用程序,所以很容易想象,它似乎并没有使用太多的CPU,因为大多数时候主线程都处于睡眠状态,等待I/O完成。我在考虑几个解决办法,但我不确定,所以我想听听你的意见。
1.我可以产生许多线程(有一个固定大小的工作线程池,以保持内存使用在一定的阈值下),但老实说,我不知道这是否可以使情况更好,每个线程都将被置于睡眠状态,就像我在当前实现中的主线程一样,加上调度程序将“浪费”大量的计算能力来切换上下文。
1.我在考虑I/O完成端口(单线程?多线程?),但这意味着放弃内存Map文件(我错了吗?)并使用标准文件操作。如果是这样的话,你能提供我一些示例代码,说明如何使用IOCP读取和详细说明给定的文件列表,而不会使阅读线程休眠吗?
任何其他的想法/建议/等将是真正的赞赏:)
谢谢。

qnakjoqk

qnakjoqk1#

在并行化任何事情之前,总是先问自己:增加的复杂性是否证明了性能提升的合理性?为了最轻松地回答这个问题,只需测试您已经拥有的最大读取吞吐量的百分比。也就是说,先测试当前读取吞吐量,然后测试最大吞吐量。不要使用理论最大值进行此计算。然后,想一想,即使是最简单的方法,也会引入多少复杂性和多少可能的问题,以获得最后的几个百分点。
正如在评论中已经提到的,这里最大的性能增益可能是通过流水线实现的(即重叠计算和I/O)。最简单的实现方法是异步读取。This thread lists multiple ways to implement asnychronous file I/O in C++
如果你不需要可移植性,就使用Windows OVERLAPPED API。Boost ASIO似乎并没有使文件I/O非常容易(还没有)。我找不到任何好的例子。
请注意,根据您的系统配置,您必须启动多个线程以完全饱和I/O带宽(特别是如果该文件夹的文件实际上驻留在多个磁盘上,这是可能的)。即使您只从一个设备读取,使用多个线程可能会(稍微)更好地减轻OS开销。

lstz6jyr

lstz6jyr2#

我的经验表明,内存Map不是特别快,所以这可能是我首先要放弃的。
线程化(显式地或通过IOCP)可能也不会有太大的好处,除非目标系统有很多磁盘驱动器,并且可以将事情分割开来,以便不同的线程在不同的物理驱动器上运行。
一旦放弃内存Map并执行显式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;
}
pn9klfpd

pn9klfpd3#

警惕多线程I/O绑定场景。这里我有一个案例,它使SSD上的速度略快,但在HDD上慢了很多。以下结果是在Windows 10上每个线程读取大文件的测试:
固态硬盘:

  • 1个线程200 MB/秒
  • 2个线程100 MB/秒

硬盘:

  • 1线程100 MB/s
  • 2个线程10 MB/s
kninwzqo

kninwzqo4#

为什么每个人都这么肯定IOCP是不可能的?使用IOCP,你基本上可以启动你想要做的每一个读取,它们将在完成时进入SW,而它们通常不会按照它们发出的顺序。然后你做你的加密工作并存储哈希值或其他东西。在此期间,一个或多个IOCP读取将完成,你可以在它们上做你的加密工作。
难道这不值得一试吗?

ujv3wf0j

ujv3wf0j5#

我可以告诉你我是如何成功解决一个非常相似的问题的。
假设我们有四个线程。每个线程都有一个路径和一个从0到3的threadnr。每个线程都需要知道文件号,所以它需要一个全局变量,如int gFileNumber[4]
线程使用finfdfirstfile/findnextfile,每当它们找到一个jpg(在我的例子中),filenumber就递增gFileNumber[threadnumber]++
现在是我认为聪明的部分。如果(threadnumber == gFileNumber[threadnumber] % 4)那么这个线程应该处理这个文件。Jist计算哈希。线程将同时计算不同文件的哈希。

相关问题