大規模なアレイを作成しないことは、パフォーマンスに劇的な影響を及ぼし、多くの最適化の失敗につながる可能性があるstatic
場合でもです。constexpr
コードの速度が桁違いに遅くなる可能性があります。変数はまだローカルであり、コンパイラは、実行可能ファイルにデータとして格納するのではなく、実行時に変数を初期化することを決定する場合があります。
次の例を考えてみましょう。
template <int N>
void foo();
void bar(int n)
{
// array of four function pointers to void(void)
constexpr void(*table[])(void) {
&foo<0>,
&foo<1>,
&foo<2>,
&foo<3>
};
// look up function pointer and call it
table[n]();
}
おそらく、テーブルからフェッチするアドレスgcc-10 -O3
にコンパイルbar()
することを期待しますjmp
が、それは起こりません。
bar(int):
mov eax, OFFSET FLAT:_Z3fooILi0EEvv
movsx rdi, edi
movq xmm0, rax
mov eax, OFFSET FLAT:_Z3fooILi2EEvv
movhps xmm0, QWORD PTR .LC0[rip]
movaps XMMWORD PTR [rsp-40], xmm0
movq xmm0, rax
movhps xmm0, QWORD PTR .LC1[rip]
movaps XMMWORD PTR [rsp-24], xmm0
jmp [QWORD PTR [rsp-40+rdi*8]]
.LC0:
.quad void foo<1>()
.LC1:
.quad void foo<3>()
これは、GCCがtable
実行可能ファイルのデータセクションに格納しないことを決定し、代わりに関数が実行されるたびにその内容でローカル変数を初期化するためです。実際、constexpr
ここで削除すると、コンパイルされたバイナリは100%同一になります。
これは、次のコードよりも簡単に10倍遅くなる可能性があります。
template <int N>
void foo();
void bar(int n)
{
static constexpr void(*table[])(void) {
&foo<0>,
&foo<1>,
&foo<2>,
&foo<3>
};
table[n]();
}
私たちの唯一の変更は、私たちが行っtable
static
たことですが、その影響は甚大です。
bar(int):
movsx rdi, edi
jmp [QWORD PTR bar(int)::table[0+rdi*8]]
bar(int)::table:
.quad void foo<0>()
.quad void foo<1>()
.quad void foo<2>()
.quad void foo<3>()
結論として、ルックアップテーブルをローカル変数にしないでくださいconstexpr
。Clangは実際にはそのようなルックアップテーブルをうまく最適化しますが、他のコンパイラーはそうではありません。実例については、コンパイラエクスプローラを参照してください。