没有内核开销的异步/重叠文件读取?

Asynchronous/Overlapped File Read without kernel overhead?

提问人:Schrottiy 提问时间:10/9/2023 最后编辑:Schrottiy 更新时间:10/9/2023 访问量:132

问:

我对 win32 ReadFile() 重叠的 I/O 并不真正感兴趣,因为它真的很不可靠,并且在 DEBUG 配置中具有 8-10 毫秒的令人不快的内核开销,在 25-30 毫秒的 RELEASE 配置(Visual Studio)中为 400 毫秒,内核开销与读取的字节大小有关......我已经报告了这个:https://developercommunity.visualstudio.com/t/Overlapping-ReadFile-takes-significantly/10475365

重现步骤:

  1. 在 Visual Studio 中创建新项目,然后选择“控制台应用”
  2. 将下面的代码复制粘贴到项目中
  3. 将“Here could be your file!!”替换为大于或等于 READ_SIZE 宏的文件路径
  4. 在 DEBUG 模式下运行几次,以读取重叠读取文件请求的平均时间
  5. 在 RELEASE 模式下运行几次,以读取重叠读取文件请求的平均时间
  6. 比较平均时间...
#include <iostream>
#include <chrono>
#include <Windows.h>

#define READ_SIZE 400000000

int main()
{
    // init
    void* data = new unsigned char[READ_SIZE];

HANDLE file = CreateFileW(L"Here could be your file!!!", // set file path to file with size of READ_SIZE 
        GENERIC_READ,
        NULL,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
        NULL);

if (file == INVALID_HANDLE_VALUE)
    {
        std::cout << "failed to CreateFile!\n";
        std::cin.ignore();
        return 1;
    }

// request overlapped read
    OVERLAPPED overlapped{};
    
std::chrono::high_resolution_clock::time_point begin = std::chrono::high_resolution_clock::now();
    ReadFile(file, data, READ_SIZE, NULL, &overlapped);
    if (GetLastError() != ERROR_IO_PENDING)
    {
        std::cout << "failed to request overlapped file read\n";
        std::cin.ignore();
        return 1;
    }
    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
    
int readFile_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();

// wait for overlapped read to complete
    begin = std::chrono::high_resolution_clock::now();
    DWORD bytesRead;
    GetOverlappedResult(file, &overlapped, &bytesRead, TRUE);
    end = std::chrono::high_resolution_clock::now();

int getOverlappedResult_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();

// print results
    std::cout << readFile_duration + getOverlappedResult_duration << "(ms) for " << bytesRead / 1000000 << "(mb)\n";
    std::cout << readFile_duration << "(ms) to request overlapped file read\n";
    std::cout << getOverlappedResult_duration << "(ms) for overlapped read to complete\n";

// cleanup
    CloseHandle(file);
    std::cin.ignore();
}

但是我不认为他们会很快解决这个问题,而且 8 毫秒对我来说似乎也有很多内核开销......我正在寻找某种直接告诉 SSD/HDD/M2 等驱动程序该做什么的方法,而不会让内核减慢速度。

我发现似乎有一种使用 IRP(I/O 请求包)的方法:
https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-irps
但是为了使用这些,我的代码必须在内核模式下运行,据我所知,这是一个非常糟糕的主意。

如果您知道是否有一种方法可以在 win32 中没有太多内核开销的异步/重叠文件读取,或者 maby 某种与平台无关的方式,或者您也许可以为我指出一个新的方向,请这样做!

C++ WinAPI 重叠 IO

评论

2赞 RbMm 10/9/2023
如何工作,而不是取决于代码的优化。同样在链接的示例中,您实际上会进行同步读取。不清楚你问什么ReadFile
2赞 RbMm 10/9/2023
我不认为他们会很快解决这个问题 - 这里根本没有什么可修复的
1赞 Jesper Juhl 10/9/2023
你真的应该发布一个最小的可重复的例子,这样人们就可以自己测试你的主张。
2赞 RbMm 10/9/2023
其他方法 - 没有其他方法(如果只是从文件中读取数据)。 非常薄的垫片.它在内核中调用 IoManager,它创建 IRP 并发送到文件系统。几乎所有的工作都是在文件系统、卷和存储堆栈中完成的。即使您将 IRP 从内核直接发送到文件系统,这也不会改变几乎任何内容(如果只是读取数据并在此之后等待)。而这一切绝对不依赖于你的代码优化或VSReadFileZwReadFile
2赞 Stuntman11 10/9/2023
我不认为你正在做的事情是被允许的。您是否阅读了没有缓冲的重叠 io 的要求FILE_FLAG_NO_BUFFERING

答:

-1赞 Paul Sanders 10/9/2023 #1

从@Stuntman给你的链接

用于读取和写入操作的文件访问缓冲区地址应与物理扇区对齐,这意味着内存中的地址与卷的物理扇区大小的整数倍一致。

我不认为你正在这样做(你得到的对齐方式可能在调试和发布版本之间有所不同)。

根据磁盘的不同,可能不会强制执行此要求。

因此,鉴于上述情况,可能会有次要影响(即缓冲区对齐)影响您的时间。

请参阅下面的@rbmm的评论。你仍然有一个对齐问题,只是不是我认为你有的问题。

评论

0赞 IInspectable 10/9/2023
operator new返回一个针对任何类型适当对齐的指针。
1赞 RbMm 10/9/2023
文档包含错误。如果 IO 未缓冲 - 缓冲区大小和文件偏移量,则扇区大小必须对齐。但是缓冲区地址 - 没有。它必须对齐,但不能针对扇区大小。但在 AlignmentRequirement + 1 上。
0赞 Paul Sanders 10/9/2023
@rbmm我明白了。OP 请注意,因为你没有这样做。答案是实时的,以免丢失rbmm的评论。
0赞 Paul Sanders 10/9/2023
@IInspectable 不是,在这种情况下,它没有。
2赞 RbMm 10/9/2023
此处的正确信息:1.传递给 ZwReadFile 或 ZwWriteFile 的任何可选 ByteOffset 都必须是扇区大小的倍数。 2.传递给 ZwReadFile 或 ZwWriteFile 的 Length 必须是扇区大小的整数。 3. 缓冲器必须根据底层设备的对齐要求进行对齐。