增长数组C ++的最有效方法

时间:2015-12-11 10:24:09

标签: c++ arrays performance memory-management

如果以前曾经问过这个道歉,我无法找到一个完全回答我想知道的问题。他们提到了这样做的方法,但没有比较方法。

我正在用C ++编写一个程序来解决PDE到稳定状态的问题。我不知道这需要多少时间步。因此,我不知道我的时间阵列会有多长。最长时间为100,000秒,但时间步长可能小到.001,因此在最坏情况下可能长达1e8 double s(也不一定是罕见情况)

在内存分配和运行时间方面实现这一目标的最有效方法是什么?

我看过的选项:

  • 动态分配一个包含1e8个元素的数组,其中大部分都不会被使用。
  • 最初分配较小的数组,在需要时创建更大的数组并通过
  • 复制元素
  • 使用std::vector及其尺寸增加功能

还有其他选择吗?

我主要关注速度,但我想了解内存的考虑因素

3 个答案:

答案 0 :(得分:3)

如果你担心速度,只需分配1e8双打并完成它。

在大多数情况下,矢量应该可以正常工作。请记住,将其追加摊销为O(1)。

除非你运行的东西非常奇怪,操作系统内存分配应该解决大多数碎片问题以及很难找到800MB空闲内存块的事实。

答案 1 :(得分:0)

了解您的计算机上哪个选项“效率最高”的唯一方法是尝试一些不同的选项和配置文件。我可能会从以下开始:

  1. std::vector以最大可能的大小构建。
  2. std::vector以保守的球场大小和push_back构建。
  3. std::dequepush_back
  4. std::vector vs std::deque辩论正在进行中。根据我的经验,当元素的数量未知且不是太大时,std::deque几乎永远不会比std::vector快(即使std::vector需要多次重新分配),但最终可能会减少使用记忆。当元素数量未知且非常大时,std::deque内存消耗似乎爆炸,std::vector显然是赢家。

    如果在分析后,这些选项都没有提供令人满意的性能,那么您可能需要考虑编写自定义分配器。

答案 2 :(得分:0)

如评论中所述,如果您小心使用vector,您实际上可以保留预先存储最大输入大小的容量(1e8双打),而无需在任何内存中进行分页。

为此,您要避免使用填充构造函数和方法,例如resize(最终会访问所有内存)并使用reservepush_back填充它并仅触摸内存如所须。这将允许大多数操作系统一次只能在您访问的vector的块中分页,而不是一次全部内容。

然而,我倾向于在这些输入规模上大部分避免这种解决方案,但原因很简单:

  1. 可能偏执的可移植性担心我可能会遇到一种没有这种按需页面行为的操作系统。
  2. 可能偏执的恐惧,分配可能无法找到一组连续的未使用页面并面临内存不足错误(这是一个灰色区域 - 我倾向于担心这个跨越千兆字节,数百兆字节的数组是边界)。
  3. 只是一种完全主观的,可能是愚蠢/过时的偏见,不会过分依赖操作系统在分配内存中进行分页的行为,而更倾向于拥有一个只需按需分配的数据结构。
  4. 调试。
  5. 在这四个中,前两个可能只是偏执狂。第三个可能只是愚蠢。然而至少在像Windows这样的操作系统上,当使用调试版本时,内存会在早期完全初始化,并且我们最终会在保留此类vector的容量时立即将分配的页面映射到DRAM。然后,我们可能最终导致轻微的启动延迟和任务管理器,即使在我们做任何事情之前,调试版本的内存使用量仍为800 MB。

    虽然调试版本的效率通常应该是一个小问题,但是当发布和调试之间的潜在差异很大时,它可能会开始使生产代码几乎无法进行有效调试。因此,如果这些差异可能很大,那么我的偏好就是“将其大块化”#34;。

    我想在这里应用的策略是分配较小的块 - 较小的N元素数组,其中N可能是512个双倍(只需要足够舒适以适合公共分母页面)大小为4千字节 - 可能减去块元数据的两倍。我们用元素填充它们,当它们变满时,创建另一个块。

    使用这些块,我们可以通过链接它们(形成展开的列表)或在单独的聚合中存储指针向量来聚合它们,这取决于是否需要随机访问或仅仅顺序访问就足够了。对于随机访问的情况,这会产生轻微的开销,但是我倾向于在这些输入标度上找到相对较小的输入标度,这些输入标度通常由存储器层次结构的上层而不是寄存器和指令级来控制。 / p>

    这可能对您的情况有些过分,谨慎使用vector可能是最好的选择。然而,如果这还不够,而且你有类似的担忧/需求,那么这种粗巧的解决方案可能有所帮助。