当我在文件中寻找某个位置并写入少量数据(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);
}
也许没有惊喜?
该OldSchool
方法名列前茅 - 很多。它的速度提高了 6 倍以上(148 万对每秒 232000 条记录)。为了确保没有遇到硬件缓存,我将数据库大小扩大到 2000 万条记录(文件大小为 763 兆)并得到了相同的结果。
在您指出对 的明显调用之前fflush
,让我说删除它没有任何效果。我想这是因为当我寻找足够远的地方时必须提交缓存,这是我大部分时间都在做的事情。
发生什么了?
在我看来,每当我尝试写入时,缓冲的 I/O 必须读取(并可能写入全部)文件的一大块。因为我几乎没有利用它的缓存,所以这是非常浪费的。
另外(而且我不知道磁盘上硬件缓存的细节),如果缓冲的 I/O 在我只更改一个扇区时试图写入一堆扇区,那会降低硬件缓存的有效性。
有没有磁盘专家可以比我的实验结果更好地评论和解释这一点?=)