匿名命名空间中的全局变量优化
Global variable optimisation in anonymous namespace
这是一个例子:
namespace {
int a = 0;
}
int main() {
a = 1;
}
GCC and Clang optimized a = 1
out, but MSVC 没有。
GCC 的输出示例:
main:
xor eax, eax
ret
另一个例子:
namespace {
int a = 0;
}
int main() {
a++;
}
None 个编译器优化了 a++
:GCC, Clang, MSVC.
GCC 的输出示例:
main:
add DWORD PTR _ZN12_GLOBAL__N_11aE[rip], 1
xor eax, eax
ret
是否有一些推理,为什么编译器优化了一些全局变量,但不优化其他变量?针对特定的编译器做出回答是可以的。
对于您的第一个示例,如果您使用 /GL
标志,MSVC 还将优化全局(并写入)。不幸的是,当你这样做时,它不会产生汇编语言输出,所以 Godbolt 只会告诉你输出文件丢失了。要检查代码,您必须生成一个实际的可执行文件,然后反汇编它,以获得:
0000000140001000: 33 C0 xor eax,eax
0000000140001002: C3 ret
...但即使 /GL
也不会优化第二种情况的增量:
0000000140001000: FF 05 AA 6B 01 00 inc dword ptr [0000000140017BB0h]
0000000140001006: 33 C0 xor eax,eax
0000000140001008: C3 ret
“为什么”很难回答,只是因为它归结为从事编译器优化的人们的动机。然而,我猜想他们主要是响应(至少是感知到的)客户的需求。因此,对于 gcc 来说,很多事情都归结为特定优化是否会显着帮助(或损害)Linux 等流行开源包的性能。同样,对于 MSVC,它主要是关于流行的软件包,如 Windows 和 Office(不,我并不是要暗示他们也忽略了他们的外部客户)。
因此,在这种情况下,您的代码在程序启动时产生的影响很小。那么最大的问题是,这是否会转化为对其他足够普遍的代码产生更大的影响,以至于他们有动力对其进行优化。我猜想至少到目前为止,他们还没有看到很多出现这种情况的案例。考虑到大多数人创建和初始化全局变量并从那时起忽略它的频率,我的猜测是他们根本没有太多理由在这种特殊情况下工作。它的优化程度仅限于它足够相似,可以受到他们主要为其他情况编写的优化的影响。例如,如果您在这个一般顺序上添加另一个修改 a
:
namespace {
int a = 0;
}
void foo()
{
for (int i=0;i<10;i++)
a++;
}
int main()
{
a++;
foo();
}
...编译器将单独的增量操作变成单个赋值可能并不奇怪:
0000000140001000: C7 05 A6 6B 01 00 mov dword ptr [0000000140017BB0h],0Bh
0B 00 00 00
000000014000100A: 33 C0 xor eax,eax
000000014000100C: C3 ret
但这只是应用基本的强度降低,这是几十年来众所周知的标准优化之一,大多数编译器几乎都做了这么长时间。我想你可能会争辩说消除全局变量只是死代码消除的一个应用,但是作为一个全局变量使它成为一种特殊情况,所以即使他们通常明确地进行死代码消除,它也没有得到在这种情况下(始终如一地)应用。
这是一个例子:
namespace {
int a = 0;
}
int main() {
a = 1;
}
GCC and Clang optimized a = 1
out, but MSVC 没有。
GCC 的输出示例:
main:
xor eax, eax
ret
另一个例子:
namespace {
int a = 0;
}
int main() {
a++;
}
None 个编译器优化了 a++
:GCC, Clang, MSVC.
GCC 的输出示例:
main:
add DWORD PTR _ZN12_GLOBAL__N_11aE[rip], 1
xor eax, eax
ret
是否有一些推理,为什么编译器优化了一些全局变量,但不优化其他变量?针对特定的编译器做出回答是可以的。
对于您的第一个示例,如果您使用 /GL
标志,MSVC 还将优化全局(并写入)。不幸的是,当你这样做时,它不会产生汇编语言输出,所以 Godbolt 只会告诉你输出文件丢失了。要检查代码,您必须生成一个实际的可执行文件,然后反汇编它,以获得:
0000000140001000: 33 C0 xor eax,eax
0000000140001002: C3 ret
...但即使 /GL
也不会优化第二种情况的增量:
0000000140001000: FF 05 AA 6B 01 00 inc dword ptr [0000000140017BB0h]
0000000140001006: 33 C0 xor eax,eax
0000000140001008: C3 ret
“为什么”很难回答,只是因为它归结为从事编译器优化的人们的动机。然而,我猜想他们主要是响应(至少是感知到的)客户的需求。因此,对于 gcc 来说,很多事情都归结为特定优化是否会显着帮助(或损害)Linux 等流行开源包的性能。同样,对于 MSVC,它主要是关于流行的软件包,如 Windows 和 Office(不,我并不是要暗示他们也忽略了他们的外部客户)。
因此,在这种情况下,您的代码在程序启动时产生的影响很小。那么最大的问题是,这是否会转化为对其他足够普遍的代码产生更大的影响,以至于他们有动力对其进行优化。我猜想至少到目前为止,他们还没有看到很多出现这种情况的案例。考虑到大多数人创建和初始化全局变量并从那时起忽略它的频率,我的猜测是他们根本没有太多理由在这种特殊情况下工作。它的优化程度仅限于它足够相似,可以受到他们主要为其他情况编写的优化的影响。例如,如果您在这个一般顺序上添加另一个修改 a
:
namespace {
int a = 0;
}
void foo()
{
for (int i=0;i<10;i++)
a++;
}
int main()
{
a++;
foo();
}
...编译器将单独的增量操作变成单个赋值可能并不奇怪:
0000000140001000: C7 05 A6 6B 01 00 mov dword ptr [0000000140017BB0h],0Bh
0B 00 00 00
000000014000100A: 33 C0 xor eax,eax
000000014000100C: C3 ret
但这只是应用基本的强度降低,这是几十年来众所周知的标准优化之一,大多数编译器几乎都做了这么长时间。我想你可能会争辩说消除全局变量只是死代码消除的一个应用,但是作为一个全局变量使它成为一种特殊情况,所以即使他们通常明确地进行死代码消除,它也没有得到在这种情况下(始终如一地)应用。