2017-11-12 16 views
5

は、人々がさまざまなライブラリに厳格なベンチマークを実行しようとしているとき、私は時々、このようなコードを参照してください。`static_cast <volatile void>`はオプティマイザを意味しますか?

auto std_start = std::chrono::steady_clock::now(); 
for (int i = 0; i < 10000; ++i) 
    for (int j = 0; j < 10000; ++j) 
    volatile const auto __attribute__((unused)) c = std_set.count(i + j); 
auto std_stop = std::chrono::steady_clock::now(); 

テスト対象のコードの結果が破棄されていることに気付かからオプテ​​ィマイザを防ぐために、ここで使用されvolatile、次に計算全体を破棄します。

テスト対象のコードが値を返さない、それはvoid do_something(int)であると言う、そして時々私はこのようなコードを参照してください。

auto std_start = std::chrono::steady_clock::now(); 
for (int i = 0; i < 10000; ++i) 
    for (int j = 0; j < 10000; ++j) 
    static_cast<volatile void> (do_something(i + j)); 
auto std_stop = std::chrono::steady_clock::now(); 

volatileのこの正しい使い方ですか? volatile voidとは何ですか?コンパイラと標準の観点からはどういう意味ですか? [dcl.type.cv]で標準(N4296)では

それは言う:

7 [注:オブジェクトの値はによって変更される可能性があるため、揮発性は、オブジェクト を含む積極的な最適化を避けるために、実装へのヒントです実装によって検出不能であることを意味します。さらに、いくつかの実装では、 がvolatileで、オブジェクトに にアクセスするために特別なハードウェア命令が必要であることを示す場合があります。詳細なセマンティクスについては、1.9を参照してください。一般的には、揮発性のセマンティクスは、それらがCであるとして、C++で 同じであることを意図している - エンドノート]セクション1.9で

それは実行モデルに関する指針の多くを指定しますが、として揮発性に関する限り、それは「volatileオブジェクトへのアクセス」に関するものです。 volatile voidにキャストされた文を実行すると、コードが正しく理解されていると仮定し、最適化バリアが生成された場合はどうなるかはわかりません。

+0

これは実際にはあまり書かれたテストではないようです。テストでは、実際のコードと比較してテスト結果の潜在的な変更を防ぐために、コンパイラを混乱させるために「揮発性」やその他のトリックを使用しないでください。 – VTT

+0

マイクロベンチマークで「volatile」とまったく同じことは、かなりコンパイラに依存しています。 'volatile void'へのキャストが特定のコンパイラで値を計算するが、実際にそれをメモリに格納する命令を費やさない場合は、テストしたい関数だけのスループットをテストするリピートループにしたい。 –

+0

次の入力に1つの反復の出力を与えると、待ち時間をテストできます。 *他の*周辺コードへの影響を数えることは別の別のことです。 (例えば、 'do_something()'がFP除算やSQRTを含む場合、スループットは非常に悪いかもしれませんが、[除算ユニットを必要としない周辺のコードへの影響は小さい](https://stackoverflow.com/questions/ 4125033 /浮動小数点除算 - 浮動小数点乗算/ 45899202#45899202)。 –

答えて

1

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の依存関係を破るものとして認識されません。

関連する問題