【发布时间】:2021-10-30 03:11:15
【问题描述】:
下面的代码(需要google benchmark)填充两个向量并将它们相加,将结果存储在第一个向量中。对于我使用Eigen::VectorXd 和std::vector 进行性能比较的向量类型:
#include <Eigen/Core>
#include <benchmark/benchmark.h>
#include <vector>
auto constexpr N = 1024u;
template <typename TVector>
TVector generate(unsigned min) {
TVector v(N);
for (unsigned i = 0; i < N; ++i)
v[i] = static_cast<double>(min + i);
return v;
}
auto ev1 = generate<Eigen::VectorXd>(0);
auto ev2 = generate<Eigen::VectorXd>(N);
auto sv1 = generate<std::vector<double>>(0);
auto sv2 = generate<std::vector<double>>(N);
void add_vectors(Eigen::VectorXd& v1, Eigen::VectorXd const& v2) {
v1 += v2;
}
void add_vectors(std::vector<double>& v1, std::vector<double> const& v2) {
for (unsigned i = 0; i < N; ++i)
v1[i] += v2[i];
}
static void eigen(benchmark::State& state) {
for (auto _ : state) {
add_vectors(ev1, ev2);
benchmark::DoNotOptimize(ev1);
}
}
static void standard(benchmark::State& state) {
for (auto _ : state) {
add_vectors(sv1, sv2);
benchmark::DoNotOptimize(sv1);
}
}
BENCHMARK(standard);
BENCHMARK(eigen);
我在 Intel Xeon E-2286M @2.40Ghz 上运行它,使用 Eigen 3.3.9、MSVC 16.11.2 以及这些相关的编译器开关 /GL、/Gy、/O2、@ 987654328@、/Oi 和 /arch:AVX。典型的输出如下所示:
Run on (16 X 2400 MHz CPU s)
CPU Caches:
L1 Data 32K (x8)
L1 Instruction 32K (x8)
L2 Unified 262K (x8)
L3 Unified 16777K (x1)
--------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------
standard 99 ns 100 ns 7466667
eigen 169 ns 169 ns 4072727
这似乎表明在 std::vector 上的操作比在 Eigen::VectorXd 上快约 69%。在反汇编中,紧密的循环如下所示:
// For Eigen::VectorXd
00007FF672221A11 vmovupd ymm0,ymmword ptr [rcx+rax*8]
00007FF672221A16 vaddpd ymm1,ymm0,ymmword ptr [r8+rax*8]
00007FF672221A1C vmovupd ymmword ptr [r8+rax*8],ymm1
00007FF672221A22 add rax,4
00007FF672221A26 cmp rax,rdx
00007FF672221A29 jge eigen+0C7h (07FF672221A37h)
00007FF672221A2B mov rcx,qword ptr [rsp+48h]
00007FF672221A30 mov r8,qword ptr [rsp+58h]
00007FF672221A35 jmp eigen+0A1h (07FF672221A11h)
// For std::vector
00007FF672221B40 vmovups ymm1,ymmword ptr [rax+rdx-20h]
00007FF672221B46 vaddpd ymm1,ymm1,ymmword ptr [rax+rcx-20h]
00007FF672221B4C vmovups ymmword ptr [rax+rcx-20h],ymm1
00007FF672221B52 vmovups ymm1,ymmword ptr [rax+rdx]
00007FF672221B57 vaddpd ymm1,ymm1,ymmword ptr [rax+rcx]
00007FF672221B5C vmovups ymmword ptr [rax+rcx],ymm1
00007FF672221B61 lea rax,[rax+40h]
00007FF672221B65 sub r8,1
00007FF672221B69 jne standard+0C0h (07FF672221B40h)
可以注意到,两者都使用vaddpd 来一次添加4 个doubles。但是,对于std::vector,编译器展开循环以在每次迭代中执行 2 个vaddpd,但对于Eigen::VectorXd 却没有这样做。另一个潜在的重要区别是std::vector 的循环对齐到 32 个字节(地址以 0x40 = 64 = 2*32 结尾)。
FWIW:我添加了 /Qvec-report:2 并且编译器报告:
[...]\Core\AssignEvaluator.h(415) : info C5002: loop not vectorized due to reason '1305'
而reason 1305 表示“类型信息不足”。
我有根据的猜测是,Eigen 使用内在函数(此处为 _mm256_add_pd)的努力会适得其反,并使编译器感到困惑。让编译器做它的事情(自动矢量化)似乎是一个更好的主意。我是否遗漏了什么,或者这可以被认为是一个 Eigen 错误(错过了优化机会)?
【问题讨论】:
标签: c++ performance vector visual-c++ eigen