【发布时间】:2013-12-05 08:49:19
【问题描述】:
我试图理解 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 导致编译器错误。
【问题讨论】:
-
你能显示
v0、...、v4的声明吗? -
@Adam:你的意思是向量初始化的反汇编?
-
不,这些向量是什么。为了省去翻阅那个相当长的文件的麻烦。
-
@Adam: gist.github.com/BernieWt/… 和 gist.github.com/BernieWt/… 请注意,两个变体的初始化反汇编是相同的。
-
这两个是等价的吗?表达式模板一个正在生成一个全新的
std::vector,然后将其分配给v4,而手环正在修改v4,我猜。更改手循环以执行相同的操作(即,通过push_back为每个外部循环创建一个新的v5)以将苹果与苹果进行比较。为了改进表达式模板,它必须知道它被分配给一个预先退出的vector,这意味着vector = expression已经支持进行就地编辑,而不仅仅是expression::operator vector。
标签: c++ optimization compiler-construction vectorization expression-templates