Armadillo:.t() 的低效链接

Armadillo: Inefficient chaining of .t()

考虑以下两种方法来做同样的事情。

arma::Mat<double> B(5000,5000,arma::fill::randu);
arma::Mat<double> C(5000,500, arma::fill::randu);

好的,内存中的两个密集矩阵。现在我想将它们乘以一个新矩阵,但 B 转置。方法一:

arma::Mat<double> A = B.t() * C;

方法二:

arma::Mat<double> Bt = B.t()
arma::Mat<double> A = Bt * C;

哪个更快?方法二!大约是 2.5 倍! 现在如果我们在乘法之前分配 A,它不会改变方法 2 的时间。它加快了方法 1,但它仍然是方法 2 的两倍。

这对我来说似乎很奇怪,因为我认为如果在编译时没有模板化的东西,机器代码将几乎相同。那么,为什么他们会以实际上使情况变得更糟的方式对其进行模板化呢?还是我漏掉了一些重要的东西?

将 B.t() 作为 Bt 存储在内存中并执行 arma::inplace_trans(B) 从时间的角度来看同样昂贵。显然 Bt = B.t() 占用更多内存,但您可以同时保留两者。我做了 B 平方,所以乘法次数与 A = B * C 相同。

A = B * C --> 6.98 秒

Bt = B.t(); A = Bt * C --> 7.02 秒

A = B.t() * C --> 18.6124秒,或者A预分配时14.56秒(??)

我进入了这个兔子洞,看看我应该如何存储 B 才能更有效率,因为我可以用另一种方式构造它。特别是当我开始提取行或列时。但是提取行和列之间的区别实际上在这个规模上是无法观察到的!要明确: A = B.rows(0, 499) * CA = B.cols(0, 499).t() * C 快很多。我知道它们在数学上并不相同,但如果我以相反的方式构建 B,我希望通过访问连续的内存块来获得一些性能优势。甚至 A = B.rows(0,499)A = B.cols(0, 499) 在成本方面几乎相同,这让我感到惊讶,但问题的范围开始变得太大了。

PS:我正在使用 OpenBLAS

大家好我要在这里回答我自己的问题可能对其他人有用。我的答案是,这是因为我使用的是通用 OpenBLAS,而不是英特尔 processor-specific 版本的 BLAS,并且 运行 处于调试模式。

编译时优化并使用 Intel processor-specific 版本的 BLAS:

  1. Bt = B.t() 然后 A = Bt * C 肯定比 A = B.t() * C 慢,正如我们预期的那样,由于中间步骤的存储。
  2. A = B.t() * CA = B * C 快,如果 B 是正方形(我知道这不是同一个数字),但差异很小,对于我的数字可能是 0-20%正在使用。
  3. 同理,A = B.rows(0, 499) * CA = B.cols(0, 499).t() * C 慢。

解释是我认为列访问比行访问快。 B.t() * C 同时使用 B 和 C 的列,而 B * C 使用 B 的行和 C 的列。

所有这些都比循环快得多。因此,在 C++ 手动循环上使用 BLAS——这比担心行与列更重要。

一个异常:A = B.rows(0, 499) 仍然比 A = B.cols(0, 499) 快。任何关于原因的想法将不胜感激!

P.S。在 C++ 中处理高于 2D 的张量的提示也将不胜感激。我讨厌 arma::Cubes 虽然我确实使用它们。