【问题标题】:c++: how to optimize IO?c++:如何优化IO?
【发布时间】:2012-04-19 07:19:43
【问题描述】:

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

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

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

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

有什么策略可以让 IO 更快?

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

有什么策略或优化吗?

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

【问题讨论】:

  • 您是否有可用内存将部分数据加载到缓存中?
  • 我会认真考虑购买 SSD,因为实施自定义解决方案(类似于数据库系统)所花费的时间很容易值得您花在磁盘上的 300 美元。另外我想激进的操作系统 IO 调度程序可能会有所帮助。
  • SSD 是个好主意。或者(假设这是在 64 位机器上有意义)购买 32 GB 内存也是一个相对便宜的选择。
  • 您有并行文件系统来支持并行 I/O 吗?
  • @AlexandreC。好吧,如果我在某处的超级计算机上运行东西,那么 SSD 是不可能的......

标签: c++ optimization io


【解决方案1】:

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

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

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

【讨论】:

  • 请原谅我的无知,但我不知道缓存或memcached 是什么。你能推荐一些介绍性的教程或解释吗(我找到了memcached库页面,但他们假设对什么是缓存有一些先验知识)。
【解决方案2】:

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

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

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

【讨论】:

  • 我是自学编程的,所以你说的很多东西对我来说都是新概念。 --> 2) 你怎么做才能避免翻译成ASCII?除了fprintffscanf 等,我什么都不知道。(有效的)替代方案是什么?
  • @CycoMatto,使用fwritefread 允许直接写入/读取二进制数据。
  • @DerekPark 谢谢。我在这里会很头疼。虽然我肯定会利用 `fwrite, fread` 更快的二进制格式,但我必须承认我也希望保留部分数据可供人类阅读。预计算相当棘手且细致入微,因此我希望能够检查一切看起来是否合理(为了稍后进行调试,我需要知道确切的值......)。我应该只保留一个单独的 ASCII 版本以进行调试吗?
  • 这总是一个挑战。人类可读性非常适合调试目的;纯二进制的读取速度要快得多。如果你两者都做,挑战就变成了确保它们真的是一样的,并且始终保持同步。首先让相同的方法负责两者。
  • @CycoMatto,使用read()write() 而不是fread()fwrite() 可以获得一些速度。不同之处在于后者为您进行缓冲区处理,而前者可以为您节省一份缓冲区副本。但是您需要学习接口 - open() 接受与 fopen() 不同的参数并返回一个整数,然后您将其传递给 write()read,而不是 FILE *
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    一种常用方法是Least Recently Used Algorithm,用于确定将交换哪个页面。

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 为避免指针问题,选择 POD 数据结构会有所帮助。对于预先计算的东西,动态性不是必需的,因此排序数组可以工作。
      • @MatthieuM。好点子。 (有一次我这样做时,我们确实将所有内容都限制在 POD 中,使用 char[] 而不是 std::string 等)
      【解决方案6】:

      一些准则:

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

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

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

      【讨论】:

        【解决方案7】:

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

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

        (通常在某些应用程序长时间运行处理不同数据时遇到)

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

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

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

        【讨论】:

          【解决方案8】:

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


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

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


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

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


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

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

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

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

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

              【解决方案10】:

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

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-01-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多