跳转到内容

优化 C++/通用优化技术/输入/输出

来自维基教科书,开放的书籍,开放的世界

以压缩格式存储文本文件

[编辑 | 编辑源代码]

磁盘的带宽远低于处理器。通过即时(解)压缩,CPU 可以加速 I/O。

文本文件往往可以很好地压缩。不过,要确保选择一个快速的压缩库;zlib/gzip 非常快,而 bzip2 速度较慢。Boost Iostreams 库包含 Gzip 过滤器,这些过滤器可用于像读取普通文件一样读取压缩文件。

namespace io = boost::iostreams;

class GzipInput : public io::filtering_istream {
    io::gzip_decompressor gzip;
    std::ifstream file;

  public:
    GzipInput(const char *path)
      : file(path, std::ios_base::in | std::ios_base::binary)
    {
        push(gzip);
        push(file);
    }
};

即使这比“原始”I/O 速度更快(例如,如果你有一个快速的固态硬盘),它仍然可以节省磁盘空间。

通常,以压缩文本文件格式存储的数据比以二进制格式表示的相同数据在磁盘上占用的空间更少,即使在二进制数据被压缩后也是如此。[1]

二进制格式

[编辑 | 编辑源代码]

不要以文本模式存储数据,而是以二进制格式存储。

平均而言,二进制数字占用的空间小于格式化数字,因此从内存到磁盘或从磁盘到内存的传输速度更快。此外,如果数据以处理器使用的相同格式传输,则无需进行代价高昂的从文本格式到二进制格式的转换,反之亦然。

使用二进制格式的一些缺点是数据不是人可读的,并且格式可能依赖于处理器架构。

打开文件

[编辑 | 编辑源代码]

不要在每次访问时都打开和关闭一个经常需要用到的文件,而是在第一次访问时打开它,并在使用完它后关闭它。

关闭和重新打开磁盘文件需要时间。因此,如果你需要经常访问一个文件,可以通过在访问它之前只打开一次文件,将它的句柄包装器提升到外部作用域,并在完成后关闭它来避免这种开销。

I/O 缓冲区

[编辑 | 编辑源代码]

不要对单个小对象或微型对象执行许多 I/O 操作,而是在包含许多对象的 4 KB 缓冲区上执行 I/O 操作。

即使运行时支持的 I/O 操作被缓冲,许多 I/O 函数的开销也比将对象复制到缓冲区要昂贵。

较大的缓冲区没有良好的局部性参考。

内存映射文件

[编辑 | 编辑源代码]

除了实时系统的关键部分,如果你需要以非顺序方式访问二进制文件的大部分内容,而不是使用 _seek_ 操作重复访问它,或者将它全部加载到应用程序缓冲区中,请使用 内存映射文件,前提是你的操作系统提供了此功能。

当你必须以非顺序方式访问二进制文件的大部分内容时,有两种标准的替代技术

  • 在不读取文件内容的情况下打开文件;并且每当需要数据时,使用文件定位操作(也称为 _seek_)跳转到数据位置,并从文件中读取该数据。
  • 分配一个与整个文件一样大的缓冲区,打开文件,将它的内容读取到缓冲区中,关闭文件;并且每当需要数据时,在缓冲区中搜索它。

使用内存映射文件,相对于第一种技术,每个定位操作都用一个简单的指针赋值操作替换,每个读取操作都用一个简单的内存到内存的复制操作替换。即使假设数据已经存在于磁盘缓存中,内存映射文件操作也比相应的文件操作快得多,因为后者需要许多系统调用。

相对于将整个文件预加载到缓冲区中的技术,使用内存映射文件具有以下优点

  • 当使用文件读取系统调用时,数据通常首先被传输到磁盘缓存中,然后传输到进程内存中,而使用内存映射文件时,可以直接访问包含从磁盘加载的数据的系统缓冲区,从而节省了复制操作和磁盘缓存空间。输出操作的情况类似。
  • 当读取整个文件时,程序会被阻塞相当长的时间,而使用内存映射文件时,这种时间会分散到整个处理过程中,只要访问文件就会一直存在。
  • 如果一些会话只需要一小部分文件,则内存映射文件只加载这些部分。
  • 如果多个进程必须将同一个文件加载到内存中,则每个进程都会为它分配内存空间,而使用内存映射文件时,操作系统会将数据的单个副本保存在内存中,供所有进程共享。
  • 当内存不足时,操作系统必须将缓冲区中即使尚未更改的部分写入交换磁盘区域,而内存映射文件的未更改页面只会丢弃。

但是,在实时系统的关键部分,不适合使用内存映射文件,因为访问数据的延迟取决于数据是否已经被加载到系统内存中,或者是否仍然只在磁盘上。

C++ 标准没有定义内存映射接口,实际上 C 接口因平台而异。Boost Iostreams 库通过提供 可移植的 RAII 风格接口 来弥补各种操作系统实现之间的差距。类似的、更精简的开源库是 cpp-mmf

  1. C2: PowerOfPlainText.
华夏公益教科书