【问题标题】:Allocating ~10GB of vectors - how can I speed it up?分配 ~10GB 的向量 - 我怎样才能加快速度?
【发布时间】:2015-11-11 20:05:10
【问题描述】:

我正在加载大约 1000 个文件,每个文件代表一个大约 300 万个浮点数的数组。我需要将它们全部放在内存中,因为我需要进行一些涉及所有这些的计算。

在下面的代码中,我已经分解了内存分配和文件读取,因此我可以分别观察每个的速度。我有点惊讶地发现内存分配比文件读取花费的时间长得多。

  std::vector<std::vector<float> * > v(matrix_count);   
  for(int i=0; i < matrix_count; i++) {
    v[i] = new std::vector<float>(array_size);
  }

  for(int i=0; i < matrix_count; i++) {
    std::ifstream is(files[i]);
    is.read((char*) &((*v[i])[0]), size);
    is.close();
  }

测量时间,分配循环需要 6.8 秒,而文件加载需要 2.5 秒。从磁盘读取的速度几乎是为其分配空间的 3 倍,这似乎违反直觉。

我可以做些什么来加快内存分配?我尝试分配一个大向量,但由于 bad_malloc 失败了——我猜 10GB 的向量不行。

【问题讨论】:

  • 1.自己使用一块连续的内存和映射索引。 2. 使用boost向量实现与避免所有向量元素零初始化的构造函数。
  • 没有理由动态分配向量内的向量。也就是说,创建向量并将它们初始化为零是相当昂贵的。相反,将文件内存映射到内存中,无论如何它们似乎类似于浮点数组的内存结构。另外,请检查大小,因为即使分配速度较慢,它也不会比慢速 IO 操作花费更长的时间。
  • 这里我倾向于同意 inf,只使用单个块分配。这将是保留空间的最有效的内存和 cpu 方式。还要确保您在具有大量内存的机器上运行。一旦你的工作开始交换,你就会迷失方向(这实际上可以解释为什么向量的创建速度较慢)。如果您真的关心速度配置文件,请使用例如 valgrind 的 callgrind 插件。当你发现为什么你的代码很慢时,你常常会感到惊讶。它往往与您的预期不同。
  • re:分配一大块,我确实提到我尝试过,但是当我尝试分配一个 10GB 向量时,我得到一个 bad_malloc 失败。我不应该遇到这个问题,即矢量应该能够处理这样的大小吗?
  • 也许你应该分析一下究竟是什么花了这么长时间。也许这很容易解决。

标签: c++ performance memory memory-management


【解决方案1】:

Is there something I could do to speed up the memory allocation? I tried allocating one large vector instead, but that failed with bad_malloc -- I guess a 10GB vector isn't ok.

我主要想通过解决这一部分来回应:bad_alloc 异常容易被误解。它们不是“内存不足”的结果——它们是系统未能找到未使用页面的连续块的结果。如果您养成尝试分配大量连续内存块的习惯,您可能有足够多的可用内存,但仍会获得bad_alloc,这仅仅是因为系统找不到连续集免费的页面。您不一定能通过“确保有足够的内存可用”来避免bad_alloc,因为您可能已经看到,在尝试分配仅 10 GB 的块时,拥有超过 100 GB 的 RAM 仍然会使您容易受到它们的攻击。避免它们的方法是以更小的块而不是一个史诗数组分配内存。在足够大的规模上,展开列表之类的结构可以开始提供优于巨大数组的良好性能,并且获得bad_alloc 异常的概率要低得多(指数级),除非您确实确实用尽了所有可用内存。实际上有一个峰值,它提供的连续性和引用位置不再有益,并且实际上可能会阻碍足够大的内存性能(主要是由于分页,而不是缓存)。

对于您正在处理的那种史诗般的规模输入,您实际上可能会从 std::deque 中获得更好的性能,因为它具有页面友好的性质(这是 deque 可以在不需要 @ 的情况下真正发光的少数情况之一987654327@ 与 vector)。如果您不需要完美的连续性,可以尝试一下。

当然,最好使用实际的分析器进行测量。它将帮助我们解决实际问题,尽管考虑到“大量大块”的类型,您可能会在这里受到内存而不是磁盘 IO 的瓶颈,这可能并不完全令人震惊(令人惊讶但也许并不令人震惊) ' 正在分配(磁盘 IO 很慢,但如果您真的对系统施加压力,内存堆分配有时会很昂贵)。这在很大程度上取决于系统的分配策略,但是如果您分配如此史诗般的内存块并大量分配,即使是平板或伙伴分配器也可能会退回到更慢的代码分支,并且分配甚至可能开始需要类似于搜索或更多访问权限的东西在那些极端情况下到二级存储(在这里,我恐怕不确定在分配这么多大块时究竟发生了什么,但我之前已经“感觉到”并测量了这些瓶颈,但在某种程度上我从来没有完全弄清楚操作系统到底在做什么——上面这段纯属猜测)。

这有点违反直觉,但分配更多的小块通常可以获得更好的性能。通常这会使事情变得更糟,但如果我们谈论的是每个内存块有 300 万个浮点数和一千个类似的内存块,那么开始分配页面友好的 4k 块可能会有所帮助。通常,提前以大块预分配内存并将其池化会更便宜,但在这种情况下,“大”更像是 4 KB 块,而不是 10 GB 块。

std::deque 通常会为您做这种事情,因此尝试看看它是否有帮助可能是最快的事情。使用std::deque,您应该能够为所有 10 GB 的内容制作一个单独的内容,而无需将其拆分为更小的内容以避免bad_alloc。它也没有一些引用的整个内容的零初始化开销,并且push_backs 即使在最坏的情况下也是恒定时间(不像std::vector 那样摊销恒定时间),所以我会尝试std::deque 与实际push_back 而不是预先调整大小并使用operator[]。您可以一次读取小块的文件内容(例如:使用 4k 字节缓冲区)并推回浮点数。无论如何都要尝试一下。

无论如何,这些都只是有根据的猜测,没有代码和分析测量,但这些都是在你测量之后尝试的一些事情。

MMF 也可能是这种情况的理想解决方案。让操作系统处理访问文件内容所需的所有棘手细节。

【讨论】:

    【解决方案2】:

    使用多个线程来分配内存和读取文件。您可以创建一组比如说 15 个线程,并让每个线程选择下一个可用作业。

    当您深入挖掘时,您会发现打开文件也有相当大的开销,通过使用多个线程可以大大减少。

    【讨论】:

      【解决方案3】:

      您不需要处理内存中的所有数据。取而代之的是,您应该使用虚拟矢量之类的东西,它会在需要时加载所需的数据。使用这种方法可以节省内存,并且不会给您带来巨大内存分配的副作用。

      【讨论】:

        猜你喜欢
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多