static_cast<volatile void> (foo())
は、コンパイラに対して、最適化を有効にしてgcc/clang/MSVC/ICCのいずれかに実際にfoo()
を計算させる方法としては機能しません。
#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);
//volatile const auto c = std_set.count(); // real work happens
static_cast<volatile void> (std_set.count()); // optimizes away
}
}
すべての4つの主要なx86のコンパイラとだけret
にコンパイルされます。 (MSVCはstd::bitset::count()
か何かのスタンドアロンの定義については、ASMを発するが、foo()
のその些細な定義については、下にスクロール。
(出典+ ASMこのため、出力とMatt Godbolt's compiler explorer上の次の例)
static_cast<volatile void>()
が何かをするコンパイラがあるかもしれません。その場合、結果をメモリに格納する命令を費やさず、計算だけを行うリピートループを書くのに軽量な方法かもしれません(これは時にはあなたがマイクロベンチマークで望むもの)
tmp += foo()
(またはtmp |=
)で結果を累積し、変数に格納する代わりに、main()
から返すか、またはprintf
で印刷することもできます。空のインラインasm
ステートメントを使用して、実際に命令を追加せずに最適化するコンパイラの能力を破るような、さまざまなコンパイラ特有のこと。
は、彼がoptimizer-escape function for GNU Cを示しChandler Carruth's CppCon2015 talk on using perf
to investigate compiler optimizationsを参照してください。しかし、彼のescape()
関数は、値がメモリ内にあることを要求するように書かれています(asmにを渡し、"memory"
clobber)。私たちはそれを必要としません。コンパイラはレジスタやメモリに値を持たせるだけで済みます。 (それはasm文がゼロの指示であることを知らないので、それは完全に私たちのループを展開する可能性は低いです。)
このコードは、gccのに、余分な保存せずにだけ POPCNTにコンパイルされます。
// just force the value to be in memory, register, or even immediate
// instead of empty inline asm, use the operand in a comment so we can see what the compiler chose. Absolutely no effect on optimization.
static void escape_integer(int a) {
asm volatile("# value = %0" : : "g"(a));
}
// simplified with just one inner loop
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
クランはかなり間抜けです"g"
制約を満たすためにメモリ内の値を置くことを選択しました。しかし、clangは、メモリをオプションとして含むインラインasm制約を与えると、それを行う傾向があります。だからチャンドラーのescape
の機能よりも優れているわけではありません。 -march=haswell
と
# 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
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の依存関係を破るものとして認識されません。
これは実際にはあまり書かれたテストではないようです。テストでは、実際のコードと比較してテスト結果の潜在的な変更を防ぐために、コンパイラを混乱させるために「揮発性」やその他のトリックを使用しないでください。 – VTT
マイクロベンチマークで「volatile」とまったく同じことは、かなりコンパイラに依存しています。 'volatile void'へのキャストが特定のコンパイラで値を計算するが、実際にそれをメモリに格納する命令を費やさない場合は、テストしたい関数だけのスループットをテストするリピートループにしたい。 –
次の入力に1つの反復の出力を与えると、待ち時間をテストできます。 *他の*周辺コードへの影響を数えることは別の別のことです。 (例えば、 'do_something()'がFP除算やSQRTを含む場合、スループットは非常に悪いかもしれませんが、[除算ユニットを必要としない周辺のコードへの影響は小さい](https://stackoverflow.com/questions/ 4125033 /浮動小数点除算 - 浮動小数点乗算/ 45899202#45899202)。 –