私は C++ の式テンプレートの概念を理解しようとしています。そのため、サンプル コードなどを組み合わせて単純なベクトルと関連する式テンプレート インフラストラクチャを生成し、バイナリ演算子 (+、-、) のみをサポートしています。
すべてがコンパイルされますが、標準の手書きループと式テンプレートのバリアントのパフォーマンスの違いが非常に大きいことに気付きました。ET は手書きの 2 倍近く遅いです。違いを期待していましたが、それほどではありませんでした。
完全なコード リストは次の場所にあります。
https://gist.github.com/BernieWt/769a4a3ceb90bb0cae9e
(乱雑なコードで申し訳ありません。)
.
要するに、私は基本的に次の 2 つのループを比較しています。
ET:
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];
}
出力を逆アセンブルすると、次のようになります。違いは明らかに、追加の memcpy と、ET バリアントのリターン中に発生するいくつかの 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 を使用してコンパイラ エラーを引き起こす方法があるかどうか疑問に思っていました。