2016-01-07 4 views
7

以下のコードは、マルチスレッドプログラミングの好奇心を示しています。特に、std::memory_order_relaxedのパフォーマンスは、単一のスレッドで通常の増分と比較して増加します。 fetch_add(relaxed)シングルスレッドの方が通常のインクリメントよりも2倍遅いのはなぜ分かりませんか?アトミックfetch_addとパフォーマンスの追加

static void BM_IncrementCounterLocal(benchmark::State& state) { 
    volatile std::atomic_int val2; 

    while (state.KeepRunning()) { 
    for (int i = 0; i < 10; ++i) { 
     DoNotOptimize(val2.fetch_add(1, std::memory_order_relaxed)); 
    } 
    } 
} 
BENCHMARK(BM_IncrementCounterLocal)->ThreadRange(1, 8); 

static void BM_IncrementCounterLocalInt(benchmark::State& state) { 
    volatile int val3 = 0; 

    while (state.KeepRunning()) { 
    for (int i = 0; i < 10; ++i) { 
     DoNotOptimize(++val3); 
    } 
    } 
} 
BENCHMARK(BM_IncrementCounterLocalInt)->ThreadRange(1, 8); 

出力:

 
     Benchmark        Time(ns) CPU(ns) Iterations 
     ---------------------------------------------------------------------- 
    BM_IncrementCounterLocal/threads:1   59   60 11402509         
     BM_IncrementCounterLocal/threads:2   30   61 11284498         
     BM_IncrementCounterLocal/threads:4   19   62 11373100         
     BM_IncrementCounterLocal/threads:8   17   62 10491608 

    BM_IncrementCounterLocalInt/threads:1  31   31 22592452         
     BM_IncrementCounterLocalInt/threads:2   15   31 22170842         
     BM_IncrementCounterLocalInt/threads:4   8   31 22214640         
     BM_IncrementCounterLocalInt/threads:8   9   31 21889704 
+1

マシンコードを比較しましたか? –

+0

シングルスレッドモードであることをコンパイラーに伝える必要があります。 –

+1

'memory_order_relaxed'が何かするシステムにいますか? x86ではそうではありません。 'fetch_add'は追加操作をアトミックにするためにバスロックを発行するでしょう。 'operator ++'はそれをする必要はありません。 –

答えて

0

ローカルバージョンはアトミックを使用していません。 (volatileを使用しているという事実は、赤いニシンです - volatileは、マルチスレッドコードでは本質的に意味がありません)。

原子バージョンは、アトミック(!)を使用してです。 1つのスレッドだけが実際に変数にアクセスするために使用されるという事実はCPUには見えないので、コンパイラがそれを見つけられなかったのは驚きではありません。 (できなくなりますほとんどないときintstd::atomic_intを回すために安全である場合を考え出すない点浪費開発者の努力もありません。彼らは、複数のスレッドからアクセスする必要がない場合は誰もatomic_intを書きません。)このように

原子バージョンは実際には原子が原子であることを確認する手間に行きます、そして率直に言って、私はそれがわずか2倍遅いことに驚いています - 私は10倍以上になると予想していました。

+0

なぜfetch_addが通常のインクリメントよりも遅いのか尋ねました。私はあなたが他の何かに答えたと思う:) – Roman

+0

"原子バージョンは、実際に原子が増加していることを確認する手間に行きます"。 'memory_order_relaxed'は、"同期や順序制約はなく、この操作には原子性のみが必要です。 "お使いのプロセッサ(x86など)に応じて、これは他のメモリオーダーと同じくらい高価になる可能性があります。 –

2

volatile intでは、コンパイラは変数の読み取り/書き込みの最適化や並べ替えを行わないようにする必要があります。

fetch_addでは、CPUは、読み取り - 変更 - 書き込み操作がアトミックであることに注意する必要があります。

アトミック性の要件とは、CPUがマシン上の他のCPUと通信して、指定されたメモリ位置を自身の読み取りと書き込みの間で読み書きしないようにすることです。コンパイラがcompare-and-swap命令を使用してfetch_addをコンパイルすると、実際には短いループが生成され、他のCPUが値を変更したケースをキャッチします。

volatile intの場合、そのような通信は必要ありません。逆に、volatileは、コンパイラが読み取りを生成しないようにする必要があります。volatileは、ハードウェアレジスタとのシングルスレッド通信用に設計されています。値を読み取るだけで副作用が発生する可能性があります。

+0

私はstd :: memory_order_relaxedを使用していますので、メモリの障壁は置かれませんでした。私はインテルプロセッサ上でワードインクリメントがそのままアトミックであるという印象を受けました。 fetch_addとCASは異なる操作です。 – Roman

+0

なぜ単語のインクリメントはそのままの状態にする必要がありますか? CPUは、まずメモリから現在の値をフェッチし、それを修正してからそれを元に戻す必要があります。これに対する任意の原子性の制約は、かなりの追加的な努力を必要とする。 CPUは、*シングルスレッド*の結果が同じであれば、他のメモリ操作での読み書きの順序を変更することさえ許されます。特殊なアトミック命令が命令セットに追加され、アトミックな 'fetch_add'演算などを可能にしました。自然にアトミックなのは、単一のアライメントされた書き込みまたは読み出しだけです。 – cmaster

関連する問題