不创建大型数组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]();
}
我们唯一的改变是我们做了tablestatic,但是影响是巨大的:
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 实际上很好地优化了这样的查找表,但其他编译器却没有。 See Compiler Explorer for a live example.