c ++:如何优化IO?

时间:2012-04-05 14:26:14

标签: c++ optimization io

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

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

我的程序的后半部分接受输入参数 D 。对于每个 D ,我需要执行大量计算,其中包含预先计算的数据(来自文件)和其他一些特定于 D 的数据。 (因此每个 D 的问题都不同。)

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

是否有任何使IO更快的策略?

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

任何策略或优化?

目前,我正在使用cstdio执行所有操作,即无iostream。那会有很大的不同吗?

10 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:6)

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

地图部分有点难,因为你无法在一次操作中完成所有操作。在这里,您需要提出一个序列化的约定。为了使i / o尽可能快,最好的办法是将它从地图转换为内存形式,这些形式都集中在一个地方,您可以轻松快速地转换回地图。例如,如果您的键是整数,并且您的值是常量大小,那么您可以创建一个键数组和一个值数组,将键复制到一个数组中并将值复制到另一个数组中,然后{{1}两个数组,也可能写出它们的大小。同样,你只需要拨打两次或三次write()的电话即可阅读。

请注意,没有任何内容转换为ASCII,并且系统调用次数最少。该文件不是人类可读的,但它将是紧凑的,并且可以快速读入。三件事使得i / o变慢:1)系统调用,如果你使用小的读/写; 2)转换为/从ASCII(printf,scanf); 3)磁盘速度。很难做到3)(除了SSD)。您可以在后台线程中进行读取,但可能需要阻止等待数据进入。

答案 2 :(得分:4)

一些指导原则:

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

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

划分和征服:如果64位不是一个选项,请尝试将数据分成大块,使得所有块永远不会一起使用,并且在需要时需要整个块。这样你可以在他们需要的时候加载这些块,并在它们不需要时丢弃它们。

答案 3 :(得分:3)

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

  1. 所有I / O时间的总和远远超过将所有数据加载到RAM的成本
  2. 在应用程序运行期间访问所有数据的相对较大部分
  3. (当某些应用程序长时间运行处理不同的数据时,通常会遇到它们)

    但是对于其他情况,可能会考虑其他选项。 例如。了解访问模式是否真正随机是至关重要的。如果不是,请查看重新排序数据以确保可以一起访问的项目彼此接近。这将确保操作系统缓存的性能最佳,并且还可以减少硬盘寻道时间(当然不是SSD的情况)。

    如果访问是真正随机的,并且应用程序没有运行,只要需要分摊一次性数据加载成本,我会考虑体系结构,例如,通过将此数据管理器提取到单独的模块中,该模块将保持此数据的预加载。

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

正如所说,尽可能多地在内存中缓存。

如果您发现需要缓存的数量大于内存允许的数量,请尝试更换内存和磁盘之间的缓存,以便在需要将虚拟内存页交换到磁盘时进行缓存。这基本上是同样的问题。

一种常见方法是Least Recently Used Algorithm,用于确定要交换的页面。

答案 6 :(得分:1)

这实际上取决于可用内存量和访问模式。


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

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


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

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


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

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

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

经常访问的值将保留在缓存中,避免不必要的往返,并且因为压缩文件会减少IO。

答案 7 :(得分:0)

以缓存有效的方式构建数据。例如,当你正在阅读“某些部分”时,如果这些部分都是连续的,那么就不必在磁盘周围寻找所有部分。

如果您与其他进程共享磁盘访问权限,则批量读取和写入将有所帮助,而不是按记录进行记录。

答案 8 :(得分:0)

  

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

据我所知,std::map也是预先计算的,并且没有插入/删除操作。只搜索。如何将地图替换为std::hash_mapsparsehash之类的内容。从理论上讲,它可以提高性能。

答案 9 :(得分:0)

  

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

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

这样可以保存和共享文件,缓存实际使用的部分并将其他部分保存在磁盘上。