16

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

更具体地说:我可以预先计算大量信息——大量的long double概率std::map<int,int>

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

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

有没有让 IO 更快的策略?

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

有什么策略或优化吗?

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

4

10 回答 10

14

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

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

于 2012-04-05T14:45:12.807 回答
6

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

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

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

于 2012-04-05T14:34:00.997 回答
4

一些指导方针:

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

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

分而治之:如果不能选择 64 位,请尝试将数据分成大块,以使所有块永远不会一起使用,并且在需要时需要整个块。这样,您可以在需要时加载块并在不需要时丢弃它们。

于 2012-04-05T14:54:50.537 回答
3

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

  1. 期间所有 I/O 时间的总和远远超过将所有数据加载到 RAM 的成本
  2. 在应用程序运行期间访问所有数据的相对大部分

(当某些应用程序长时间运行处理不同的数据时,通常会遇到它们)

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

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

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

于 2012-04-05T14:58:19.630 回答
2

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

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

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

于 2012-04-05T14:33:28.363 回答
1

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

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

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

于 2012-04-05T14:39:19.147 回答
1

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


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

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


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

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


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

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

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

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

于 2012-04-05T16:25:37.087 回答
0

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

如果您与另一个进程共享磁盘访问权限,那么批量读取和写入而不是逐条记录会有所帮助。

于 2012-04-05T14:36:40.420 回答
0

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

据我了解,这些std::map也是预先计算的,并且没有插入/删除操作。只能搜索。将地图替换为std::hash_mapsparsehash之类的想法怎么样?从理论上讲,它可以提高性能。

于 2012-04-05T16:52:14.500 回答
0

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

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

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

于 2012-04-05T20:08:54.813 回答