static_cast<volatile void> (foo())
不能作为一种方法来要求编译器在启用优化的情况下实际计算foo()
,无论是在gcc / clang / MSVC / ICC中都不行。
#include <bitset>
void foo() {
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j) {
std::bitset<64> std_set(i + j);
static_cast<volatile void> (std_set.count());
}
}
使用所有4个主要的x86编译器编译后,只会生成一个ret
指令。(MSVC为独立定义的
std::bitset::count()
等发出汇编代码,但向下滚动查看其
foo()
的平凡定义。)
(此示例和下一个示例的源代码和汇编输出请参见Matt Godbolt的编译器浏览器)
也许有一些编译器可以在
static_cast<volatile void>()
中执行某些操作,在这种情况下,这可能是编写重复循环的轻量级方法,它不会花费指令将结果存储到内存中,只计算它。(这有时可能是微基准测试中所需的)。
使用
tmp += foo()
(或
tmp |=
)累积结果,并从
main()
返回它或使用
printf
打印它,也可以代替存储到
volatile
变量中。或者使用各种编译器特定的内容,例如使用空的内联
asm
语句来破坏编译器的优化能力,而不实际添加任何指令。
请查看
Chandler Carruth在CppCon2015上关于使用perf
调查编译器优化的演讲,他展示了GNU C的
逃逸优化函数。但是他的
escape()
函数需要将值存储在内存中(将一个
void*
传递给汇编语句,并使用
"memory"
占位符)。我们不需要这样做,我们只需要编译器将值存储在寄存器、内存或即时常量中。(由于编译器不知道汇编语句是零条指令,所以很可能无法完全展开我们的循环。)
这段代码在gcc编译器上只编译成popcnt指令,没有额外的存储操作。
static void escape_integer(int a) {
asm volatile("# value = %0" : : "g"(a));
}
void test1() {
for (int i = 0; i < 10000; ++i) {
std::bitset<64> std_set(i);
int count = std_set.count();
escape_integer(count);
}
}
#gcc8.0 20171110 nightly -O3 -march=nehalem (for popcnt instruction):
test1():
# value = 0 # it peels the first iteration with an immediate 0 for the inline asm.
mov eax, 1
.L4:
popcnt rdx, rax
# value = edx # the inline-asm comment has the %0 filled in to show where gcc put the value
add rax, 1
cmp rax, 10000
jne .L4
ret
Clang选择将值放入内存以满足“g”约束,这相当愚蠢。但是当您给它一个包括内存选项的内联汇编约束时,clang往往会这样做。因此,对于此问题,它并不比Chandler的escape函数更好。
# clang5.0 -O3 -march=nehalem
test1():
xor eax, eax
#DEBUG_VALUE: i <- 0
.LBB1_1: # =>This Inner Loop Header: Depth=1
popcnt rcx, rax
mov dword ptr [rsp - 4], ecx
# value = -4(%rsp) # inline asm gets a value in memory
inc rax
cmp rax, 10000
jne .LBB1_1
ret
使用-march=haswell
的ICC18会做以下事情:
test1():
xor eax, eax #30.16
..B2.2: # Preds ..B2.2 ..B2.1
# optimization report
# %s was not vectorized: ASM code cannot be vectorized
xor rdx, rdx # breaks popcnt's false dep on the destination
popcnt rdx, rax #475.16
inc rax #30.34
# value = edx
cmp rax, 10000 #30.25
jl ..B2.2 # Prob 99% #30.25
ret #35.1
很奇怪,ICC使用了xor rdx,rdx
而不是xor eax,eax
。这浪费了一个REX前缀,并且在Silvermont/KNL上没有被识别为依赖项破坏。
volatile
或其他技巧来混淆编译器,以防止与实际代码相比潜在地改变测试结果。 - user7860670volatile
在微基准测试中的作用取决于编译器。如果将其转换为volatile void
可以让特定编译器计算一个值,但不会花费指令将其实际存储到内存中,那么这可能是您想要在重复循环中测试所需函数吞吐量的方式。 - Peter Cordesdo_something()
涉及FP除法或SQRT,则它的吞吐量可能非常低,但也会对不需要除法单元的周围代码产生较小的影响。 - Peter Cordes