【问题标题】:how to memory map a huge matrix?如何内存映射一个巨大的矩阵?
【发布时间】:2011-01-29 20:22:06
【问题描述】:

假设您有一个巨大的(40+ GB)特征值(浮点)矩阵,行是不同的特征,列是样本/图像。

该表是按列预先计算的。 然后它被完全按行和多线程访问(每个线程加载一整行)几次。

处理这个矩阵的最佳方法是什么?我特别琢磨了5点:

  1. 由于它在 x64 PC 上运行,我可以一次对整个矩阵进行内存映射,但这有意义吗?
  2. 多线程的影响(还有多线程初始计算?)?
  3. 矩阵如何布局:行还是列?
  4. 在预计算完成后将矩阵标记为只读有帮助吗?
  5. 可以使用 http://www.kernel.org/doc/man-pages/online/pages/man2/madvise.2.html 之类的东西来加快速度吗?

【问题讨论】:

  • 这个问题可能会因为 太有趣 而被关闭——但我希望不会。对操作系统有限制吗? (从链接猜测 Linux。)
  • 我不明白为什么它会被关闭,我错过了一些规则吗?是的,该软件目前仅限于 Linux。但也欢迎提供有关 Windows 的答案。

标签: c++ d mmap


【解决方案1】:

内存映射整个文件可以使这个过程更容易。

您希望对数据进行布局以针对最常见的访问模式进行优化。听起来数据将被写入一次(按列)并读取多次(按行)。这表明数据应按行优先顺序存储。

在预计算完成后将矩阵标记为只读可能不会提高性能(有一些可能的低级优化,但我认为没有任何实现它们),但它可以防止意外出现错误写入您不打算写入的数据。也可以。

madvise 最终可能会很有用,一旦您编写好应用程序并开始工作。

我的总体建议:以最简单的方式编写程序,首先按顺序编写程序,然后在整个事物和各种主要操作周围放置计时器。确保主要操作时间与总时间相加,这样您就可以确保您没有遗漏任何东西。然后将您的性能改进工作针对实际花费最多时间的组件。

根据 JimR 在他的评论中提到的 4MB 页面,您可能最终想要研究 hugetlbfs 或使用具有透明大页面支持的 Linux 内核版本(合并为 2.6.38,可能会修补到早期版本中)。这可能会为您节省大量的 TLB 未命中,并说服内核以足够大的块执行磁盘 IO,以分摊任何寻道开销。

【讨论】:

  • 如果您没有正确访问内存,您可能会陷入混乱。如果您发现这很慢,请确保您测量输入/输出页面错误。 zvrba 涵盖了您将在他的回答中看到的一些问题,尤其是#3。我在 90 年代初做过类似的事情(从 200 到 1G),而因故障进出而造成的颠簸彻底毁了它。这是在 64MB 的 RAM 被认为是最大的时候。如果您可以将页面大小从 4096 更改为我认为的 4MB,则可以减少抖动(通过减少开销)。
  • > 40Gb,我认为我们可以假设它对于主内存来说太大了。所以一个幼稚的实现(正如这里所建议的那样)确实会导致“thrash fest”。
  • 我可能被宠坏了,但我确实可以使用内存比这更多的机器。无论如何,除非计算阶段真的很繁重,否则仅按顺序读取数据将花费与程序其余部分一样多的时间。明智的“幼稚”实现将按顺序读取数据,因此在该限制下获得基本上完整的性能。
【解决方案2】:
  1. 也许,见下文。
  2. 所有线程的总工作集大小不能超过可用 RAM,否则程序会因为交换而以蜗牛的速度运行。
  3. 只要遵守条件 2,布局就应该匹配访问模式。
  4. “标记为只读”是什么意思?
  5. 测量它。

Re 3:如果你有,例如,8 个 CPU,但没有足够的 RAM 来加载 8 行,你应该让每个线程在可管理的块中按顺序处理它的行。在这种情况下,矩阵的块布局是有意义的。如果线程必须在内存中有整行来处理它,恐怕你不能使用所有的 CPU,因为进程将开始颠簸,即从内存中踢出矩阵的一些子集并重新加载另一个需要的子集。这比完全交换稍微好一点,因为矩阵永远不会被修改,因此页面的内容不需要在被踢出之前写入交换文件。但它仍然严重影响性能。

此外,从多个线程执行随机访问 I/O 是一个坏主意,如果您使用 mmap(),您最终会这样做。你(大概)只有一个磁盘,并行 I/O 只会让它变慢。所以 mmap() 可能没有意义,您可以通过将数据顺序读取到 ram 中来获得更好的 I/O 性能。

请注意,40GB 大约是 1050 万页 4096 字节。通过执行 mmap(),在最坏的情况下,您将通过许多硬盘寻道减慢计算速度。每次搜索 8 毫秒(取自维基百科),你最终会浪费 83666 秒,即几乎一整天!

【讨论】:

  • 嗯,单行大约是几 MB 加上我有 12GB RAM,所以这不是问题。
  • 好的。但映射仍然可能会产生大量随机 I/O。
【解决方案3】:

如果您可以将整个内容放入主内存中,那么可以:内存映射所有内容,无论是列主要还是行主要都无关紧要。但是,在 40+ Gb 时,我确信它对于主内存来说太大了。在这种情况下:

  1. 不,不要映射整个事物!至少,如果您将其全部映射,不要指望内存能像普通内存一样工作。如果您没有正确处理 i/o 问题,您的程序将永远执行下去。
  2. 如果您将其存储为 row-major(听起来您没有多线程列写入),多线程访问问题就解决了。
  3. 您应该按行排列,假设每个单元格写入一次,然后读取多次。
  4. 是的,我认为在写入矩阵后将其标记为只读会有所帮助,但纯粹是为了防止错误(意外写入)。它不会影响性能。
  5. 不,再多聪明的内核预读也无法解决您的性能问题。你需要在算法层面解决它。

我认为你会遇到一个天真的实现的性能问题。写入时出现抖动的计算机(如果您将其存储为主要行)或在查询时将出现抖动(如果您将其存储为主要列)。后者可能更糟,但这是一个双向的问题。

正确的解决方案是使用既不是行优先也不是列优先而是“大方块”的中间表示。获取前 50,000 列并将它们存储在内存映射文件中(阶段 1)。它是列专业还是行专业都没关系,因为它将纯粹驻留在内存中。然后,取出每一行并将其写入最终的行主要内存映射文件(第 2 阶段)。然后对接下来的 50,000 列重复该循环,依此类推。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-06
    • 2013-10-05
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-23
    • 2019-10-05
    相关资源
    最近更新 更多