磁盘I/O期间窗帘后面发生了什么?

pad*_*ddy 17 c file-io buffer

当我寻找文件中的某个位置并写入少量数据(20个字节)时,幕后会发生什么?

我的理解

据我所知,可以从磁盘写入或读取的最小数据单元是一个扇区(传统上是512字节,但该标准现在正在改变).这意味着要写20个字节,我需要读取整个扇区,在内存中修改一些并将其写回磁盘.

这是我期望在无缓冲I/O中发生的事情.我还希望缓冲的I/O做大致相同的事情,但要聪明一点.所以我会想到,如果我通过随机搜索和写入来打破窗口的局部性,缓冲和无缓冲的I/O应该具有相似的性能......也许无缓冲的出现稍微好一些.

然后,我知道缓冲I/O仅缓冲一个扇区是疯狂的,所以我也可能期望它执行得非常糟糕.

我的应用程序

我正在存储由SCADA设备驱动程序收集的值,该驱动程序接收超过十万点的远程遥测.文件中有额外的数据,每条记录是40个字节,但在更新期间只需要写入20个字节.

实施前基准

为了检查我是否不需要想出一些出色的过度设计的解决方案,我已经使用写入文件的几百万个随机记录进行测试,该文件可能包含总共200,000条记录.每个测试都使用相同的值对随机数发生器进行种子处理.首先,我擦除文件并将其填充到总长度(大约7.6兆),然后循环几百万次,将随机文件偏移量和一些数据传递给两个测试函数之一:

void WriteOldSchool( void *context, long offset, Data *data )
{
    int fd = (int)context;
    lseek( fd, offset, SEEK_SET );
    write( fd, (void*)data, sizeof(Data) );
}

void WriteStandard( void *context, long offset, Data *data )
{
    FILE *fp = (FILE*)context;
    fseek( fp, offset, SEEK_SET );
    fwrite( (void*)data, sizeof(Data), 1, fp );
    fflush(fp);
}
Run Code Online (Sandbox Code Playgroud)

也许没有惊喜?

这个OldSchool方法排在首位 - 很多.它的速度提高了6倍(148万,而每秒232000条记录).为了确保我没有遇到硬件缓存,我将数据库大小扩展到了2000万条记录(文件大小为763兆字节)并得到了相同的结果.

在你指出明显的呼吁之前fflush,让我说删除它没有任何效果.我想这是因为当我寻找足够远的地方时必须提交缓存,这正是我大部分时间都在做的事情.

发生什么了?

在我看来,每当我尝试写入时,缓冲的I/O必须读取(并且可能全部写入)大块文件.因为我几乎没有利用它的缓存,这非常浪费.

另外(我不知道磁盘上硬件缓存的细节),如果缓冲I/O在我只改变一个扇区时试图编写一堆扇区,那么会降低硬件缓存的有效性.

是否有任何磁盘专家可以比我的实验结果更好地发表评论和解释?=)

Jam*_*arp 5

实际上,至少在我使用 GNU libc 的系统上,似乎 stdio 在写回更改的部分之前正在读取 4kB 块。对我来说似乎是假的,但我想当时有人认为这是个好主意。

我通过编写一个简单的C程序来打开一个文件,一次写入一小部分数据,然后退出;然后在 strace 下运行它,看看它实际触发了哪些系统调用。以 10000 的偏移量写入,我看到了这些系统调用:

lseek(3, 8192, SEEK_SET)                = 8192
read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1808) = 1808
write(3, "hello", 5)                    = 5
Run Code Online (Sandbox Code Playgroud)

似乎您想在这个项目中坚持使用低级 Unix 风格的 I/O,是吗?

  • 4kB 可能是文件系统的块大小(我认为这是 ext4 中的默认值)。如果你愿意,你可以改变它。 (3认同)
  • @dave:嗯,这是一个有趣的理论。但我不明白:读-修改-写循环引入了_更多_数据竞争,而不是更少。底层系统调用提供了只有内核才能提供的一定程度的原子性。 (2认同)

bdo*_*lan 5

C 标准库函数执行额外的缓冲,通常针对流式读取进行优化,而不是随机 IO。在我的系统上,我没有观察到 Jamey Sharp 看到的虚假读取,当偏移量未与页面大小对齐时,我只看到虚假读取 - 可能是 C 库总是尝试将其 IO 缓冲区对齐到 4kb 或某物。

在您的情况下,如果您在一个相当小的数据集上进行大量随机读取和写入,您可能最好使用pread/pwrite以避免必须进行搜索系统调用,或者只是简单地mmaping 数据集并将其写入内存(如果您的数据集适合内存,则可能是最快的)。