【问题标题】:Why do I see a performance drop when using row-major order?为什么我在使用行优先顺序时会看到性能下降?
【发布时间】:2017-11-26 21:49:30
【问题描述】:

我有一段代码在一个大矩阵上运行并计算按列分类的统计信息,其中的 bin 在向量 b 中给出。

代码是这样的:

for (item = 0; item < items; item++) {
    uint8 bin = binvec[item];
    for (col = 0; col < columns; col++) {
        int idx = item * items_stride + col * cols_stride;
        uint8 val = matrix[idx];
        float x = matrix2[idx];
        count[bin][val][col] += x;
    }
}

假设在编译时列数是已知的。 matrix 的值没有特定的结构/顺序 - 假定为纯随机值。 数据量相当大:几百万个项目和数百列。

查看代码,我认为在以下情况下会实现最佳性能:

  1. matrix 是主要行,用于更好的缓存局部性。
  2. count 将作为count[bin][col][val] 访问,因此可以优化count[bin][col] 地址的计算,从而更容易预取等。

但是,在将matrix 创建为主要列并按照代码中出现的顺序访问count 时,我获得了最佳性能。

尝试使用选项 (1) 或 (2) 会导致约 50% 的运行时间损失。 这违背了我对缓存局部性和编译器优化、矢量化等的直觉。

知道为什么吗?这真的让我很困惑。

【问题讨论】:

  • 是的,我预测您在此处编写代码的方式会更加优化,尤其是如果列数是编译时常数,因为编译器可以展开该循环(在某种程度上)。不幸的是,如果没有minimal reproducible example,我很难进一步调查这个问题。我无法验证您的计时结果;我无法分析常见编译器生成的代码;我无法尝试任何自己的优化来测试它们的效果。我所能做的就是说,“是的,这很奇怪。”对了,什么是“10x”?
  • 10x 非常感谢 :) 无论矩阵内存布局和对count 的访问如何,都可以完成循环展开,所以我怀疑情况是否如此。不过我会尽快添加一个示例。

标签: c caching optimization nested-loops simd


【解决方案1】:

我有点困惑。在您的示例中,矩阵是行专业的。您能否分享这两种实现而不考虑计数访问?

您的内部循环会遍历列,因此如果缓存线一次覆盖多个列,那么行主会更好。

至于计数,您的 val 取决于存储在矩阵中的内容,而列是按顺序排列的,因此如果您以这种方式访问​​计数:

count[bin][val][col]

如果列中有多个具有相等 val 的连续行,则从缓存中获取数据。但是以这种方式访问​​它:

count[bin][col][val]

您从缓存中获取数据的机会基本上为零,因为您在增加 col 后跳得太远了。这是我在这部分最好的选择。

您的矩阵(提供 val 的矩阵)是否像您想象的那样随机?

【讨论】:

  • 对不起,应该澄清一下。我使用线性索引访问矩阵,知道每个维度的步幅。对所有矩阵的访问也是如此。我已经相应地修复了代码示例。
  • 至于矩阵 - 列是独立绘制的。每列本身就是一个马尔可夫过程,每个样本的平均值由前一个样本给出,因此后续列条目的值可能接近,但不太可能相同。
  • @Moti 所以基本上在第一种情况下,你有 items_stride == ncols, cols_stride == 1。从另一个角度解决问题,是否可以对矩阵进行二次采样并运行 cachegrind 来检查它是否缓存确实会影响性能吗?
  • 但是,正如您所提到的,有可能存在零行,这可能确实对缓存更友好。我去看看,谢谢!
  • 我对cachegrind没有经验,我去看看。再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-25
  • 1970-01-01
  • 1970-01-01
  • 2014-11-12
  • 1970-01-01
  • 2014-01-08
  • 2017-03-03
相关资源
最近更新 更多