我正在研究一个数学问题,它具有能够“预先计算”大约一半问题的优势,将此信息保存到文件中,然后多次重复使用以计算我的问题的各种“实例” 。困难在于上传所有这些信息以解决实际问题是一个主要的瓶颈。
更具体地说:
我可以预先计算大量的信息 - 大量的概率(long double
),大量的std::map<int,int>
等等 - 并将所有这些东西保存到磁盘(几个Gb)。
我的程序的后半部分接受输入参数 D 。对于每个 D ,我需要执行大量计算,其中包含预先计算的数据(来自文件)和其他一些特定于 D 的数据。 (因此每个 D 的问题都不同。)
有时我需要从文件中挑选出某些预先计算好的信息。其他时候,我需要从(大)文件上传每一段数据。
是否有任何使IO更快的策略?
由于其他原因,我已经将程序并行化(MPI,通过boost::mpi
),但无论如何,访问磁盘上的文件会使我的计算时间无法忍受。
任何策略或优化?
目前,我正在使用cstdio
执行所有操作,即无iostream
。那会有很大的不同吗?
答案 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)
一些指导原则:
理想情况下,我会尝试将所有长双精度放入内存映射文件,并将所有映射到二进制文件中。
划分和征服:如果64位不是一个选项,请尝试将数据分成大块,使得所有块永远不会一起使用,并且在需要时需要整个块。这样你可以在他们需要的时候加载这些块,并在它们不需要时丢弃它们。
答案 3 :(得分:3)
当满足两个条件时,将整个数据上传到RAM的这些建议很好:
(当某些应用程序长时间运行处理不同的数据时,通常会遇到它们)
但是对于其他情况,可能会考虑其他选项。 例如。了解访问模式是否真正随机是至关重要的。如果不是,请查看重新排序数据以确保可以一起访问的项目彼此接近。这将确保操作系统缓存的性能最佳,并且还可以减少硬盘寻道时间(当然不是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_map或sparsehash之类的内容。从理论上讲,它可以提高性能。
答案 9 :(得分:0)
更具体地说:我可以预先计算大量的信息 - 大量的概率(长双),大量的std :: map等等 - 并将所有这些东西保存到磁盘(几个Gb)。 / p>
不要重新发明轮子。我建议使用键值数据存储,例如berkeley db:http://docs.oracle.com/cd/E17076_02/html/gsg/C/concepts.html
这样可以保存和共享文件,缓存实际使用的部分并将其他部分保存在磁盘上。