我试图理解 C++ 中表达式模板的概念,因此我拼凑了一些示例代码等,以生成一个简单的向量和相关的表达式模板基础结构,以仅支持二进制运算符 (+、-、)。
一切都可以编译,但是我注意到标准手写循环与表达式模板变体之间的性能差异非常大。ET 的速度几乎是手写速度的两倍。我预计会有所不同,但并没有那么大。
完整的代码清单可以在这里找到:
https://gist.github.com/BernieWt/769a4a3ceb90bb0cae9e
(为混乱的代码道歉。)
.
简而言之,我本质上是在比较以下两个循环:
东部时间:
for (std::size_t i = 0 ; i < rounds; ++i)
{
v4 = ((v0 - v1) + (v2 * v3)) + v4;
total += v4[0];
}
硬件:
for (std::size_t i = 0 ; i < rounds; ++i)
{
for (std::size_t x = 0; x < N; ++x)
{
v4[x] = (v0[x] - v1[x]) + (v2[x] * v3[x]) + v4[x];
}
total += v4[0];
}
当我反汇编输出时,会产生以下内容,不同之处显然是在 ET 变体返回期间发生的额外 memcpy 和几个 64 位加载:
Standard Loop | Expression Template
----------------------------------------+--------------------------------
L26: | L12:
xor edx, edx | xor edx, edx
jmp .L27 | jmp .L13
L28: | L14:
movsd xmm3, QWORD PTR [rsp+2064+rdx*8] | movsd xmm3, QWORD PTR [rsp+2064+rdx*8]
L27: | L13:
movsd xmm2, QWORD PTR [rsp+1040+rdx*8] | movsd xmm1, QWORD PTR [rsp+1552+rdx*8]
movsd xmm1, QWORD PTR [rsp+16+rdx*8] | movsd xmm2, QWORD PTR [rsp+16+rdx*8]
mulsd xmm2, QWORD PTR [rsp+1552+rdx*8] | mulsd xmm1, QWORD PTR [rsp+1040+rdx*8]
subsd xmm1, QWORD PTR [rsp+528+rdx*8] | subsd xmm2, QWORD PTR [rsp+528+rdx*8]
addsd xmm1, xmm2 | addsd xmm1, xmm2
addsd xmm1, xmm3 | addsd xmm1, xmm3
movsd QWORD PTR [rsp+2064+rdx*8], xmm1 | movsd QWORD PTR [rsp+2576+rdx*8], xmm1
add rdx, 1 | add rdx, 1
cmp rdx, 64 | cmp rdx, 64
jne .L28 | jne .L14
| mov dx, 512
| movsd QWORD PTR [rsp+8], xmm0
| lea rsi, [rsp+2576]
| lea rdi, [rsp+2064]
| call memcpy
movsd xmm3, QWORD PTR [rsp+2064] | movsd xmm0, QWORD PTR [rsp+8]
sub rcx, 1 | sub rbx, 1
| movsd xmm3, QWORD PTR [rsp+2064]
addsd xmm0, xmm3 | addsd xmm0, xmm3
jne .L26 | jne .L12
我的问题是:此时我被困在如何删除副本上,我基本上想在没有副本的情况下更新 v4 。关于如何去做这件事的任何想法?
注意 1:我已经尝试过 GCC 4.7/9、Clang 3.3、VS2010/2013 - 我在提到的所有编译器上都得到了大致相同的性能配置文件。
注意2:我也尝试过为 vec 前向声明 bin_exp,然后添加以下赋值运算符并从 bin_exp 中删除转换运算符,但无济于事:
template<typename LHS, typename RHS, typename Op>
inline vec<N>& operator=(const bin_exp<LHS,RHS,Op,N>& o)
{
for (std::size_t i = 0; i < N; ++i) { d[i] = o[i]; }
return *this;
}
更新注 2 中提出的解决方案实际上是正确的。并且确实导致编译器生成与手写循环几乎相同的代码。
.
另一方面,如果我将 ET 变体的用例重写如下:
auto expr = ((v0 - v1) + (v2 * v3)) + v4;
//auto& expr = ((v0 - v1) + (v2 * v3)) + v4; same problem
//auto&& expr = ((v0 - v1) + (v2 * v3)) + v4; same problem
for (std::size_t i = 0 ; i < rounds; ++i)
{
v4 = expr
total += v4[0];
}
发生崩溃是因为在 ET 实例化期间生成的临时变量(右值)在分配之前被销毁。我想知道是否有任何方法使用 C++11 导致编译器错误。