gpt4 book ai didi

c++ - Eigen:编码风格对性能的影响

转载 作者:IT老高 更新时间:2023-10-28 12:32:30 24 4
gpt4 key购买 nike

从我读到的关于 Eigen (here) 的内容来看,operator=() 似乎充当了惰性求值的“障碍”——例如它会导致 Eigen 停止返回表达式模板并实际执行(优化的)计算,将结果存储到 = 的左侧。

这似乎意味着一个人的“编码风格”会对性能产生影响——即使用命名变量来存储中间计算的结果可能会对性能产生负面影响,因为它会导致计算的某些部分被评估“太早了”。

为了验证我的直觉,我写了一个例子,结果让我大吃一惊(full code here):

using ArrayXf  = Eigen::Array <float, Eigen::Dynamic, Eigen::Dynamic>;
using ArrayXcf = Eigen::Array <std::complex<float>, Eigen::Dynamic, Eigen::Dynamic>;

float test1( const MatrixXcf & mat )
{
ArrayXcf arr = mat.array();
ArrayXcf conj = arr.conjugate();
ArrayXcf magc = arr * conj;
ArrayXf mag = magc.real();
return mag.sum();
}

float test2( const MatrixXcf & mat )
{
return ( mat.array() * mat.array().conjugate() ).real().sum();
}

float test3( const MatrixXcf & mat )
{
ArrayXcf magc = ( mat.array() * mat.array().conjugate() );

ArrayXf mag = magc.real();
return mag.sum();
}

上面给出了 3 种不同的方法来计算复值矩阵中的系数大小之和。

  1. test1 有点像“一次一步”地进行计算的每个部分。
  2. test2 在一个表达式中完成整个计算。
  3. test3 采用“混合”方法——使用一些中间变量。

我有点期待,由于 test2 将整个计算打包到一个表达式中,Eigen 将能够利用这一点并全局优化整个计算,从而提供最佳性能。

但是,结果令人惊讶(显示的数字是每个测试执行 1000 次的总微秒数):

test1_us: 154994
test2_us: 365231
test3_us: 36613

(这是使用 g++ -O3 编译的 -- 详情请参阅 the gist。)

我预计最快的版本(test2)实际上是最慢的。另外,我预计最慢的版本(test1)实际上在中间。

所以,我的问题是:

  1. 为什么 test3 的性能比替代品好这么多?
  2. 是否可以使用一种技术(无需深入研究汇编代码)来了解 Eigen 是如何实际实现您的计算的?
  3. 是否有一套指导方针可以在您的 Eigen 代码中实现性能和可读性(中间变量的使用)之间的良好权衡?

在更复杂的计算中,在一个表达式中执行所有操作可能会妨碍可读性,因此我有兴趣找到编写可读性和高性能代码的正确方法。

最佳答案

看起来像是 GCC 的问题。英特尔编译器给出了预期的结果。

$ g++ -I ~/program/include/eigen3 -std=c++11 -O3 a.cpp -o a && ./a
test1_us: 200087
test2_us: 320033
test3_us: 44539

$ icpc -I ~/program/include/eigen3 -std=c++11 -O3 a.cpp -o a && ./a
test1_us: 214537
test2_us: 23022
test3_us: 42099

icpc 版本相比,gcc 似乎在优化您的 test2 方面存在问题。

要获得更精确的结果,您可能需要通过 -DNDEBUG 关闭调试断言,如图所示 here .

编辑

关于问题 1

@ggael 给出了一个很好的答案,即 gcc 无法矢量化 sum 循环。我的实验还发现 test2 和手写的 naive for-loop 一样快,无论是 gcc 还是 icc,这表明向量化是原因,并且通过下面提到的方法在 test2 中没有检测到临时内存分配,表明 Eigen 正确地评估了表达式。

关于问题 2

避免中间内存是Eigen使用表达式模板的主要目的。所以 Eigen 提供了一个宏 EIGEN_RUNTIME_NO_MALLOC和一个简单的函数,使您能够检查在计算表达式期间是否分配了中间内存。您可以找到示例代码 here .请注意,这可能仅适用于 Debug模式。

EIGEN_RUNTIME_NO_MALLOC - if defined, a new switch is introduced which can be turned on and off by calling set_is_malloc_allowed(bool). If malloc is not allowed and Eigen tries to allocate memory dynamically anyway, an assertion failure results. Not defined by default.

关于问题 3

有一种方法可以使用中间变量,同时获得惰性求值/表达式模板引入的性能改进。

方法是使用具有正确数据类型的中间变量。不要使用指示要计算表达式的 Eigen::Matrix/Array,而应使用表达式类型 Eigen::MatrixBase/ArrayBase/DenseBase 以便表达式仅缓冲但未评估。这意味着您应该将表达式存储为中间体,而不是表达式的结果,条件是该中间体将仅在以下代码中使用一次。

由于在表达式类型 Eigen::MatrixBase/... 中确定模板参数可能会很痛苦,因此您可以改用 auto。您可以在 this page 中找到一些关于何时应该/不应该使用 auto/expression 类型的提示。 . Another page还告诉您如何将表达式作为函数参数传递而不评估它们。

根据@ggael 的回答中关于 .abs2() 的指导性实验,我认为另一个准则是避免重新发明轮子。

关于c++ - Eigen:编码风格对性能的影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37658651/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com