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
、/D "NDEBUG"
、/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 个 double
。但是,对于 std::vector
,编译器对循环进行了展开,每次迭代执行 2 次 vaddpd
,但是对于 Eigen::VectorXd
,它没有这样做。另一个可能重要的区别是,std::vector
的循环对齐到 32 字节(地址以 0x40 = 64 = 2*32 结尾)。顺便提一下:我已经添加了
/Qvec-report:2
,编译器报告如下:[...]\Core\AssignEvaluator.h(415) : info C5002: loop not vectorized due to reason '1305'
而 1305 原因代码 意味着“缺少类型信息”。
我的猜测是,Eigen 使用内部函数(例如 _mm256_add_pd
)实现向量化可能会适得其反,混淆编译器。让编译器自行进行自动向量化似乎是一个更好的想法。我是否有所遗漏?或者这可以被视为 Eigen 的缺陷(错失的优化机会)?
add_vectors
函数中针对std::vector
重载的循环边界改为v2.size()
(而不是N
),则性能会明显改善,使Eigen库成为最佳选择(197ns x 739ns)。 - Cassio Neri