为什么大块的文件I / O比小块大?

时间:2011-05-18 07:40:59

标签: windows winapi file-io readfile

如果以大小为32 MB的方式调用ReadFile一次,则显着长于读取块大小较小的等效字节数(如32 KB)。

为什么?

(不,我的磁盘不忙。)


编辑1:

忘记提及 - 我正在使用FILE_FLAG_NO_BUFFERING执行此操作!


编辑2:

怪异...

我再也无法访问旧机器了(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字节

有什么想法吗?

6 个答案:

答案 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)

您可能观察到的是,当使用较小的块时,第二个数据块可以在第一个数据块被处理时被读取,然后第三个数据块被读取而第二个数据块被处理,等等,因此速度限制是物理读取时间或处理时间较慢。如果处理一个块需要花费相同的时间来读取下一个块,则速度可能是处理和读取分开时的两倍。使用较大的块时,在处理第一个块时读取的数据量将限制为小于块大小的量。当代码为下一个数据块做好准备时,它的一部分将被读取,但其中一些不会被读取;因此,在获取剩余数据时,代码必须等待。