如果以大小为32 MB的方式调用ReadFile
一次,则显着长于读取块大小较小的等效字节数(如32 KB)。
为什么?
(不,我的磁盘不忙。)
忘记提及 - 我正在使用FILE_FLAG_NO_BUFFERING
执行此操作!
怪异... 的
我再也无法访问旧机器了(PATA),但是当我在那里进行测试时,它花了大约2倍的时间,有时更多。在我的新机器(SATA)上,我的差异只有25%左右。
这是一段要测试的代码:
#include <memory.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int main()
{
HANDLE hFile = CreateFile(_T("\\\\.\\C:"), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING /*(redundant)*/, NULL);
__try
{
const size_t chunkSize = 64 * 1024;
const size_t bufferSize = 32 * 1024 * 1024;
void *pBuffer = malloc(bufferSize);
DWORD start = GetTickCount();
ULONGLONG totalRead = 0;
OVERLAPPED overlapped = { 0 };
DWORD nr = 0;
ReadFile(hFile, pBuffer, bufferSize, &nr, &overlapped);
totalRead += nr;
_tprintf(_T("Large read: %d for %d bytes\n"),
GetTickCount() - start, totalRead);
totalRead = 0;
start = GetTickCount();
overlapped.Offset = 0;
for (size_t j = 0; j < bufferSize / chunkSize; j++)
{
DWORD nr = 0;
ReadFile(hFile, pBuffer, chunkSize, &nr, &overlapped);
totalRead += nr;
overlapped.Offset += chunkSize;
}
_tprintf(_T("Small reads: %d for %d bytes\n"),
GetTickCount() - start, totalRead);
fflush(stdout);
}
__finally { CloseHandle(hFile); }
return 0;
}
结果:
大读:1076表示67108864字节
小读数:842表示67108864字节
有什么想法吗?
答案 0 :(得分:1)
这不是Windows特有的。我用C ++ iostream库做了一些测试,发现读取有一个最佳的缓冲区大小,超过这个大小会降低性能。不幸的是,我不再有测试,我不记得大小是什么:-)。至于为什么,有很多问题,例如大缓冲区可能导致其他应用程序同时运行的分页(因为缓冲区无法被分页)。
答案 1 :(得分:1)
当你执行1024 * 32KB读取时,你是一遍又一遍地读入同一个内存块,还是你总共分配了32MB到rad并填充整个32MB?
如果您正在将较小的读取读入相同的32K内存块,那么时间差可能只是Windows不必清除额外的内存。
根据问题的FILE_FLAG_NO_BUFFERING
添加进行更新:
我不是100%肯定,但我相信当使用FILE_FLAG_NO_BUFFERING
时,Windows会将缓冲区锁定到物理内存中,这样它就可以让设备驱动程序处理物理地址(例如直接DMA)进入缓冲区)。它可以(我相信)通过将大量请求分解为更小的请求来做到这一点,但我怀疑微软可能会有这样的理念:“如果你要求FILE_FLAG_NO_BUFFERING
,那么我们假设你知道你在做什么,我们“不会妨碍你。”
当然,一次锁定32MB而不是一次32KB将需要更多资源。所以这有点像我最初的猜测,但是在物理内存级别而不是虚拟内存级别。
但是,由于我不为MS工作而且无法访问Windows源代码,因此当我与Windows内核和设备驱动程序模型密切合作时,我会模糊回忆(所以这更多或者少猜测。
答案 2 :(得分:1)
您的测试包括读取文件元数据所需的时间,特别是文件数据到磁盘的映射。如果您关闭文件句柄并重新打开它,您应该为每个句柄获得相似的时间。我在本地进行了测试以确保。
由于需要读取更多的文件到磁盘映射,因此重度碎片可能会产生更严重的影响。
编辑:为了清楚起见,我在本地运行了这个更改,并且看到了几乎相同的大小读取时间。重用相同的文件句柄,我看到了原始问题的类似时间。答案 3 :(得分:0)
当你完成FILE_FLAG_NO_BUFFERING
时,意味着操作系统不会缓冲I / O.因此,每次调用read函数时,它都会进行系统调用,该调用将从磁盘中获取每个时间。然后,如果使用较少的缓冲区大小来读取具有固定大小的一个文件,则需要更多的系统调用,以便为内核空间和每次启动磁盘I / O提供更多的用户空间。相反,如果您使用更大的块大小,那么要读取相同的文件大小,所需的系统调用将更少,因此用户到内核空间的开关会更少,并且磁盘I / O启动的次数也会更少。这就是为什么通常较大的块需要较少的时间来阅读。
尝试一次只读取1
个字节而不进行缓冲,然后尝试使用4096
个字节块然后查看差异。
答案 4 :(得分:0)
我认为可能的解释是使用FILE_FLAG_NO_BUFFERING
进行命令排队,因为这会将DMA读取直接指向低级别。
单个大型请求当然仍然必须分解为子请求,但这些请求可能会一个接一个地发送(因为驱动程序需要锁定页面并且很可能不愿意锁定几个兆字节,以免达到配额。)
另一方面,如果您在驱动程序中抛出十二或二十个请求,它只会将它们转发到磁盘和磁盘并利用NCQ。
嗯,这就是我想的可能原因(这并不能解释为什么缓冲读取会发生完全相同的现象,例如我在上面链接的Q中)。
答案 5 :(得分:0)
您可能观察到的是,当使用较小的块时,第二个数据块可以在第一个数据块被处理时被读取,然后第三个数据块被读取而第二个数据块被处理,等等,因此速度限制是物理读取时间或处理时间较慢。如果处理一个块需要花费相同的时间来读取下一个块,则速度可能是处理和读取分开时的两倍。使用较大的块时,在处理第一个块时读取的数据量将限制为小于块大小的量。当代码为下一个数据块做好准备时,它的一部分将被读取,但其中一些不会被读取;因此,在获取剩余数据时,代码必须等待。