c++:如何优化IO

c++: how to optimize IO?

本文关键字:优化 IO 何优化 c++      更新时间:2023-10-16

我正在处理一个数学问题,它的优点是能够"预先计算"大约一半的问题,将这些信息保存到文件中,然后多次重用它来计算我的问题的各种"实例"。困难在于,上传所有这些信息以解决实际问题是一个主要瓶颈。

更具体地说:我可以预先计算大量的信息——大量的概率(long double)、大量的std::map<int,int>等等——并将所有这些信息保存到磁盘上(几Gb)。

程序的后半部分接受一个输入参数D。对于每个D,我需要执行大量计算,这些计算涉及预先计算的数据(来自文件)和一些特定于D的其他数据的组合(因此每个D

有时我需要从文件中挑选出某些预先计算好的信息。其他时候,我需要从一个(大)文件上传每一条数据。

有什么策略可以加快IO的速度吗?

由于其他原因,我已经将程序并行化了(MPI,通过boost::mpi),但无论如何,访问磁盘上的文件会让我的计算时间变得难以忍受。

有什么策略或优化吗?

目前,我使用cstdio做所有事情,即没有iostream。这会有很大的不同吗?

当然,最快(但最脆弱)的解决方案是将数据mmap发送到固定地址。将其全部放入一个大的struct中,并使用分配器实例化std:::map,分配器将在附加到结构末尾的块中进行分配。这并不简单,但会很快;一次调用mmap,数据就在您的(虚拟)内存中。因为你在mmap中强制使用地址,你甚至可以存储指针等。

如上所述,除了需要大量的工作外,它还很脆弱。重新编译你的应用程序,目标地址可能不可用,或者布局可能不同,或者其他什么。但由于这实际上只是一个优化,这可能不是一个问题;任何时候出现兼容性问题,只需删除旧文件并重新开始。它会使破坏兼容性的更改后的第一次运行速度非常慢,但如果你不经常破坏兼容性。。。

地图中没有的东西很容易。你把所有的东西都放在你知道的一个连续的内存块中(比如一个大数组,或者一个没有指针的结构/类),然后用write()把它写出来。稍后使用read()在单个操作中读取它。如果大小可能不同,则使用一个操作读取具有该大小的单个int,分配内存,然后使用单个read()将其拉入。

地图部分有点难,因为你不可能在一次操作中完成所有操作。在这里,你需要想出一个序列化它的约定。为了使i/o尽可能快,你最好的办法是将它从映射转换为内存中的形式,它在一个地方,你可以轻松快速地转换回映射。例如,如果你的键是int,而你的值是恒定大小的,那么你可以制作一个键数组和一个值数组,将键复制到一个数组中,将值复制到另一个数组,然后write()这两个数组,可能也会写出它们的大小。同样,您只需调用两三次read()就可以读取内容。

请注意,没有任何东西被翻译成ASCII,并且系统调用的数量是最少的。这个文件不是人可读的,但它会很紧凑,而且读起来很快。有三件事会使i/o变慢:1)如果你使用小的读/写,系统调用;2) ASCII转换(printf、scanf);3) 磁盘速度。很难对3)(除了SSD)做太多。您可以在后台线程中进行读取,但可能需要阻止等待数据进入。

一些准则:

  • 多次调用read()比单次调用更昂贵
  • 二进制文件比文本文件快
  • 对于"多个"的大值,单个文件比多个文件快
  • 如果可以,请使用内存映射文件
  • 使用64位操作系统让操作系统为您管理内存

理想情况下,我会尝试将所有长的doubles放入内存映射文件中,并将所有映射放入二进制文件中。

分而治之:如果64位不是一种选择,那么试着把你的数据分成大块,这样所有的数据块永远不会一起使用,在需要的时候需要整个数据块。这样,你可以在需要的时候加载块,在不需要的时候丢弃它们。

当满足两个条件时,将整个数据上传到RAM的这些建议是好的:

  1. 期间所有I/O时间的总和远大于将所有数据加载到RAM的成本
  2. 在应用程序运行期间,所有数据中有相当大的一部分正在被访问

(当一些应用程序长时间运行处理不同的数据时,通常会遇到这种情况)

然而,在其他情况下,可能会考虑其他选择。例如,必须了解访问模式是否真的是随机的。如果没有,请查看重新排序数据,以确保可以一起访问的项目彼此接近。这将确保操作系统缓存处于最佳状态,也将减少HDD寻道时间(当然,SSD不是这样)。

如果访问真的是随机的,并且应用程序没有运行到需要的时间来减少一次性数据加载成本,我会研究架构,例如,通过将该数据管理器提取到单独的模块中,以保持该数据的预加载。

对于Windows,它可能是系统服务,对于其他操作系统,可以使用其他选项。

缓存,缓存,缓存。如果只有几GB,那么将大部分(如果不是全部)数据缓存在memcached之类的东西中应该是可行的。如果您在多台机器上使用MPI,而不是在同一台机器上只使用多个处理器,那么这是一个特别好的解决方案。

如果它都在同一台机器上运行,如果有可用的内存,请考虑共享内存缓存。

此外,请确保您的文件写入是在一个单独的线程上完成的。无需阻塞等待文件写入的整个进程。

如前所述,在内存中尽可能多地缓存。

如果您发现需要缓存的数量大于内存允许的数量,请尝试在内存和磁盘之间交换缓存——当虚拟内存页需要交换到磁盘时,通常是这样做的。本质上是同一个问题。

一种常见的方法是最近使用最少的算法,用于确定将交换哪个页面。

它实际上取决于可用内存的多少以及访问模式是什么


最简单的解决方案是使用内存映射文件。这通常要求文件的布局就像对象在内存中一样,因此您只需要使用不带指针的POD数据(但可以使用相对索引)。

你需要研究你的访问模式,看看你是否可以将经常一起使用的值组合在一起。这将有助于操作系统更好地缓存这些值(即,将它们保存在内存中,而不是总是去磁盘读取它们)。


另一种选择是将文件拆分为几个块,最好是以逻辑方式。可能需要创建一个索引文件,将一系列值映射到包含这些值的文件。

然后,您只能访问所需的一组文件。


最后,对于复杂的数据结构(内存映射文件失败)或稀疏读取(当您只从给定文件中提取一小段信息时),了解LRU缓存可能会很有趣。

其想法是使用序列化压缩。您编写了几个文件,其中包含一个索引,并对所有文件进行压缩(zip)。然后,在启动时,首先加载索引并将其保存在内存中。

每当你需要访问一个值时,你首先会尝试你的缓存,如果不是,你会访问包含它的文件,在内存中解压缩,并将其内容转储到你的缓存中注意:如果缓存太小,你必须挑剔你转储的内容…或者减小文件的大小

频繁访问的值将保留在缓存中,避免不必要的往返,并且因为文件是压缩的,所以IO会更少。

以缓存有效的方式构建数据。例如,当您读取"某些片段"时,如果这些片段都是连续的,则不必在磁盘周围寻找来收集所有片段。

如果您正在与另一个进程共享磁盘访问权限,则批量读取和写入(而不是逐个记录)将有所帮助。

更具体地说:我可以预先计算大量的信息——大量的概率(长双)、大量的std::map等等——并将所有这些信息保存到磁盘(几Gb)。

据我所知,std::map也是预先计算的,没有插入/删除操作。仅搜索。不如把映射替换成std::hash_map或sparsehash之类的东西。理论上,它可以提高性能。

更具体地说:我可以预先计算大量的信息——大量的概率(长双)、大量的std::map等等——并将所有这些信息保存到磁盘(几Gb)。

不要重新发明轮子。我建议使用一个键值数据存储,比如berkeleydb:http://docs.oracle.com/cd/E17076_02/html/gsg/C/concepts.html

这将允许保存和共享文件,缓存您实际大量使用的部件,并将其他部件保存在磁盘上。