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) * C
比 A = 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:
Bt = B.t()
然后 A = Bt * C
肯定比 A = B.t() * C
慢,正如我们预期的那样,由于中间步骤的存储。
A = B.t() * C
比 A = B * C
快,如果 B 是正方形(我知道这不是同一个数字),但差异很小,对于我的数字可能是 0-20%正在使用。
- 同理,
A = B.rows(0, 499) * C
比 A = 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 虽然我确实使用它们。
考虑以下两种方法来做同样的事情。
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) * C
比 A = 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:
Bt = B.t()
然后A = Bt * C
肯定比A = B.t() * C
慢,正如我们预期的那样,由于中间步骤的存储。A = B.t() * C
比A = B * C
快,如果 B 是正方形(我知道这不是同一个数字),但差异很小,对于我的数字可能是 0-20%正在使用。- 同理,
A = B.rows(0, 499) * C
比A = 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 虽然我确实使用它们。