C fread()和缓冲区管理

时间:2018-05-01 15:03:17

标签: c optimization io fread stdio

我们假设我正在解析二进制记录文件。该文件可以包含任意数量的记录。格式为:

  • 12字节C-string
  • 4个字节长度
  • 4个字节的偏移量

我打算使用fread()从文件中获取记录,并将它们转换为内部数据结构。据我了解,fread()在数据文件上有自己的内部缓冲区,以减少小读取的硬盘访问。

所以有两种选择:

  • 循环:使用fread()一次读取20个字节并解析它。这很容易,但缺点是我在每个循环中对fread()进行函数调用。它没有系统调用那么糟糕,但它仍然会每20个字节产生一次开销。在一个极端的替代情况下(比如,一次一个字节的文件解压缩)我可以在每个字节进行函数调用
  • 一次用fread()读取一个大块,然后循环:一次遍历我的副本20个字节,并解析它。解析本地内存很快,但这有一个缺点,就是我“缓冲缓冲区” - 也就是说,填充内部fread()缓冲区,这样我就可以转向memcpy()它到另一个结构。此外,它有点乏味 - 要么我必须自己将整个文件放在RAM中,要么我必须管理我自己的缓冲区填充和刷新例程以便以块的形式读取文件。

所以我在这里有点束缚,因为这两种解决方案在理论上似乎都不是最佳的!

我的问题是:是否有任何使用顺序文件IO的模式可以避免这两个这些缺点?(并且请不要“只是简介”评论 - 问题不是“哪一个更好”,而是“有第三种选择”)

1 个答案:

答案 0 :(得分:0)

  

所以我在这里有点束缚,因为这两种解决方案在理论上似乎都不是最佳的!

  

请不要“只是简介”评论 - 问题不是“哪一个更好”,而是“还有第三种选择”)

嗯,大多数系统都有fread的替代品。例如,内存映射文件,async IO,read()。此外,通常有许多方法来“配置”不同类型的文件访问,例如,对于内存映射文件,您可以选择填充整个文件文件。

但是,没有一种方法可以被视为更好的解决方案。无论你采用哪种方法,你都会得到:

只是简介

对于方法1,你写:

  

这很简单,但缺点是我在每个循环中对fread()进行函数调用。

这与“似乎理论上最优”一起是关键。你在没有证据的情况下做出假设。你不能只是通过说“似乎”来评判表现。

在编译和执行期间发生极端优化的现代计算机系统上,仅仅通过查看C代码来猜测性能几乎是不可能的。

你怎么知道每20个字节的fread调用与磁盘访问相比是个问题呢?

再一次,它会带你回到:

只是简介

为了好玩,我使用不同数量的nmemb fread在我的Linux机器上做了一些简单的基准测试。程序读取1G文件并在每个读取的块中使用1个字节计算一个简单的运行总和。

文件冷(即未缓存)时的结果

nmemb        time (sec)
   1             15.68
   2             15.76
   4             15.91
   8             15.93
  20             15.60
1000             15.60

文件热(即缓存)时的结果

nmemb        time (sec)
   1            13.90
   2             7.04
   4             3.79
   8             2.07
  20             1.09
1000             0.42

所以在我的系统上,当文件很冷时,块大小(nmemb)无关紧要。对于大多数应用程序,我会认为冷文件“正常情况”,但肯定有些应用程序可能会多次读取同一文件。

如果文件很热(即由于先前的读取而被缓存),则块大小很重要。但随着块大小的增加,差异迅速减小。已经在20比1000,差异非常小。

从上面我可以得出结论:每次只读20个字节以保持代码简单。

对于我的特定系统,以上所有测量/结论都是有效的 。根据CPU和磁盘系统以及操作系统的类型/速度,另一个系统可能会产生完全不同的结果。

所以再一次,它让我们回到:

只是简介