さまざまなプラットフォームのintやlongInterlocked.Increment(ref x)
よりも速いですか遅いですか?x++
6 に答える
アクションがアトミックに発生するように強制し、メモリバリアとして機能し、命令の周囲でメモリアクセスを並べ替えるプロセッサの機能を排除するため、速度は遅くなります。
スレッド間で共有できる状態でアクションをアトミックにする場合は、Interlocked.Incrementを使用する必要があります。これは、x++の完全な代替となることを意図したものではありません。
私たちの経験では、WindowsでのInterlockedIncrement()などは非常に大きな影響を及ぼします。あるサンプルケースでは、インターロックを排除し、代わりに++/--を使用することができました。これだけで、実行時間が140秒から110秒に短縮されました。私の分析では、インターロックはメモリのラウンドトリップを強制します(そうでない場合、他のコアはそれをどのように見ることができますか?)。L1キャッシュの読み取り/書き込みは約10クロックサイクルですが、メモリの読み取り/書き込みは100に近いです。
このサンプルケースでは、インクリメント/デクリメント操作の数を約10億と推定しました。したがって、2Ghz CPUでは、これは+ +/--の場合は5秒、インターロックの場合は50秒のようになります。違いを複数のスレッドに広げ、30秒近くにします。
少し考えてみるIncrement
と、インクリメント演算子を単純に適用するよりも呼び出しが速くなることはないことがわかります。もしそうなら、インクリメント演算子のコンパイラの実装はIncrement
内部的に呼び出し、同じように実行します。
しかし、自分でテストしてわかるように、同じようには機能しません。
2つのオプションの目的は異なります。通常、インクリメント演算子を使用します。Increment
操作をアトミックにする必要があり、その変数の他のすべてのユーザーもインターロックされた操作を使用していることが確実な場合に使用します。(すべてが協力しているわけではない場合、それは実際には役に立ちません。)
遅いです。ただし、これは、スカラー変数でスレッドセーフを実現するために私が知っている最もパフォーマンスの高い一般的な方法です。
レジスタを更新するだけでなく、CPUバスロックを実行する必要があるため、常に遅くなります。ただし、最新のCPUはレジスタに近いパフォーマンスを実現しているため、リアルタイム処理でも無視できます。
私のパフォーマンステスト:
揮発性:65,174,400
ロック:62,428,600
連動:113,248,900
TimeSpan span = TimeSpan.FromSeconds(5);
object syncRoot = new object();
long test = long.MinValue;
Do(span, "volatile", () => {
long r = Thread.VolatileRead(ref test);
r++;
Thread.VolatileWrite(ref test, r);
});
Do(span, "lock", () =>
{
lock (syncRoot)
{
test++;
}
});
Do(span, "interlocked", () =>
{
Interlocked.Increment(ref test);
});