ロックステートメントはどれくらい高価ですか?


111

私はマルチスレッドと並列処理を実験しており、基本的なカウントと処理速度の統計分析を行うためのカウンターが必要でした。クラスの同時使用に関する問題を回避するために、クラスのプライベート変数にロックステートメントを使用しました。

private object mutex = new object();

public void Count(int amount)
{
 lock(mutex)
 {
  done += amount;
 }
}

しかし、私は不思議に思っていました...変数のロックはどれほど高価ですか パフォーマンスへの悪影響は何ですか?


10
変数のロックはそれほど高価ではありません。回避したいのは、ロックされた変数での待機です。
Gabe

53
別の競合状態の追跡に何時間も費やすよりもはるかに安価です;-)
BrokenGlass

2
まあ...ロックが高価な場合は、必要なロックが少なくなるようにプログラミングを変更して、ロックを回避したい場合があります。なんらかの同期を実装できました。
Kees C. Bakker、2011年

1
ロックブロックから多くのコードを移動するだけで、(@ Gabeのコメントを読んだ後)パフォーマンスが劇的に向上しました。結論:これからは、可変アクセス(通常は1行)のみをロックブロック内に残します。これは、「ジャストインタイムロック」のようなものです。それは意味がありますか?
heltonbiker

2
@heltonbikerもちろん意味があります。また、アーキテクチャの原則である必要があります。ロックはできるだけ短く、シンプルかつ高速にする必要があります。同期する必要がある本当に必要なデータのみ。サーバーボックスでは、ロックのハイブリッドな性質も考慮する必要があります。コードにとって重要でなくても競合は、ロックのハイブリッドな性質によるものです。ロックが他の誰かによって保持されている場合、アクセスのたびにコアがスピンします。スレッドが一時停止される前の一定期間、サーバー上の他のサービスから一部のCPUリソースを効果的に消費しています。
ipavlu 2017

回答:


86

こちらがコストになる記事です。短い答えは50nsです。


39
短いより良い答え:50ns +他のスレッドがロックを保持している場合、待機に費やされた時間。
ハーマン

4
より多くのスレッドがロックに出入りするほど、より高価になります。コストはスレッドの数に
応じて

16
一部のコンテキスト:3Ghz x86で2つの数値を除算するには、約10nsかかります(命令のフェッチ/デコードにかかる時間は含まれません)。(キャッシュされていない)メモリからレジスタに単一の変数をロードするには、約40nsかかります。つまり、50nsはめちゃくちゃ目がくらむほど高速lockです。変数を使用するコストを気にするよりも、使用するコストを気にする必要はありません。
BlueRaja-ダニープフルフフト2015

3
また、この質問が尋ねられたとき、その記事は古いものでした。
オーティス

3
間違いなく「ほぼ無料」の非常に優れた指標。皆さんは考慮に入れていません。それは短くて速いだけで、競合がない場合にのみ、1つのスレッドであるということです。そのような場合、あなたはまったくロックする必要はありません。2番目の問題は、ロックはロックではなくハイブリッドロックです。アトミック操作に基づいてロックが誰によっても保持されていないことをCLR内で検出し、そのような場合、オペレーティングシステムコアへの呼び出しを回避します。テスト。25nsから50nsとして測定されるのは、実際には、ロックが取得されない場合のアプリケーションレベルのインターロックされた命令コードです
ipavlu

50

技術的な答えは、これを定量化することは不可能であり、CPUメモリのライトバックバッファーの状態と、プリフェッチャーが収集して破棄して再度読み取る必要のあるデータの量に大きく依存します。どちらも非常に非決定的です。大きな失望を回避するために、150のCPUサイクルをエンベロープの逆近似として使用しています。

実際の答えは、ロックをスキップできると思うときにコードのデバッグに費やす時間よりもずっと安いということです。

正確な数値を取得するには、測定する必要があります。Visual Studioには、拡張機能として使用できる洗練された同時実行アナライザーがあります。


1
実際には、それは定量化して測定することができます。コードの周りにこれらのロックを記述するほど簡単ではありません。そして、それはすべて50nsであると述べています。神話は、ロックへのシングルスレッドアクセスで測定されます。
ipavlu 2015年

8
「ロックをスキップできると思います」 ...多くの人がこの質問を読んだとき、そこにいると思います...
Snoop

30

参考文献:

一般的な同期プリミティブに関心があり、異なるシナリオとスレッド数に応じて、モニター、C#ロックステートメントの動作、プロパティ、コストを掘り下げている記事をいくつか紹介したいと思います。CPUの浪費とスループット期間に特に関心があり、複数のシナリオでどれだけの作業をプッシュできるかを理解します。

https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introduction https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies https:// www。 codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking

元の答え:

まあ!

THE ANSWERは本質的に正しくないため、ここでフラグが付けられた正解のようです!回答の著者には、リンク先の記事を最後まで読んでいただきますようお願いいたします。論文

2003年の記事の著者は、Dual Coreマシンのみで測定しており、最初の測定ケースではシングルスレッドのみでロック測定し、結果としてロックアクセスあたり約50nsでした。

並行環境でのロックについては何も言われていません。したがって、記事を読み続ける必要があり、後半では、2つおよび3つのスレッドを使用するロックシナリオを測定しました。これにより、今日のプロセッサの同時実行レベルに近づきます。

したがって、作者は、デュアルコアに2つのスレッドがある場合、ロックのコストは120ns、3つのスレッドがある場合は180nsになると述べています。そのため、同時にロックにアクセスするスレッドの数に明らかに依存しているようです。

したがって、それは単純です。ロックが役に立たなくなるシングルスレッドでない限り、50 nsではありません。

考慮すべきもう1つの問題は、平均時間として測定されることです。

反復時間が測定される場合、単純に大部分が高速であったために1ミリ秒から20ミリ秒の間の時間さえありますが、プロセッサ時間を待っているスレッドはほとんどなく、ミリ秒も長い遅延が発生します。

これは、高スループット、低遅延を必要とするあらゆる種類のアプリケーションにとって悪いニュースです。

そして最後に考慮すべき問題は、ロック内の処理が遅くなる可能性があることです。これは、多くの場合そうです。コードのブロックがロック内で実行される時間が長いほど、競合が高くなり、遅延が非常に高くなります。

2003年からすでに10年以上が経過していることを考慮してください。これは、完全に同時に実行するように特別に設計された数世代のプロセッサであり、ロックによってパフォーマンスが大幅に低下しています。


1
明確にするために、この記事では、アプリケーションのスレッド数によってロックのパフォーマンスが低下するとは言っていません。ロックをめぐって競合するスレッドの数が増えると、パフォーマンスが低下します。(上記の回答では、暗黙のうちに明示されていませんが)
Gooseberry 2018年

「つまり、同時にアクセスされるスレッドの数に明らかに依存しているようで、それより悪いのです」はい、言い回しの方がいいかもしれません。ロックに同時にアクセスするスレッドとして「同時にアクセスされる」ことを意味し、それにより競合が生じました。
ipavlu 2018年

20

これはパフォーマンスに関するクエリには答えませんが、.NET Frameworkは、別のオブジェクトを手動でロックしなくてもメンバーにInterlocked.Addを追加できるメソッドを提供していると言えるでしょう。amountdone


1
はい、これがおそらく最良の答えです。しかし、主に、コードがより短く、よりクリーンなためです。速度の違いは目立ちません。
Henk Holterman、2011年

この答えをありがとう。ロックを使っていろいろなことをしています。追加されたintは多くの1つです。提案を愛し、これから使用します。
Kees C. Bakker

ロックなしのコードが潜在的に高速であるとしても、ロックははるかに、はるかに簡単に正しく実行できます。Interlocked.Add自体には、同期なしの+ =と同じ問題があります。
格納庫

10

lock (Monitor.Enter / Exit)は非常に安価で、WaithandleやMutexなどの代替手段よりも安価です。

しかし、それが(少し)遅い場合は、間違った結果を伴う高速なプログラムを作成したいと思いませんか?


5
はは…私は速いプログラムと良い結果を得ようとしていました。
Kees C. Bakker、2011年

@ henk-holtermanステートメントには複数の問題があります。最初にこの質問と回答が明確に示したように、全体的なパフォーマンスに対するロックの影響についての理解は低く、シングルスレッド環境でのみ適用できる約50nsの神話を述べている人々でさえです。次に、あなたの声明はここにあり、何年もの間、プロセッサはコアで成長しますが、コアの速度はそれほどではありません。多くのコアの環境でのロックとその数の増加、2、4、8、10、20、16、32
ipavlu

私の通常のアプローチは、可能な限り相互作用が少ない疎結合の方法で同期を構築することです。これは、ロックのないデータ構造に非常に高速になります。開発を簡素化するためにスピンロックのコードラッパー用に作成しました。TPLに特別な同時コレクションがある場合でも、制御、場合によってはいくつかのコードを実行する必要があるため、独自のアラウンドリスト、配列、辞書、およびキューのスピンロックコレクションを開発しました。スピンロック。TPLコレクションでは実行できない複数のシナリオを解決でき、パフォーマンスとスループットが大幅に向上します。
ipavlu 2015年

7

タイトなループでのロックのコストは、ロックなしの代替と比較して莫大です。何度もループする余裕があっても、ロックよりも効率的です。そのため、ロックフリーキューは非常に効率的です。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LockPerformanceConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            const int LoopCount = (int) (100 * 1e6);
            int counter = 0;

            for (int repetition = 0; repetition < 5; repetition++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    lock (stopwatch)
                        counter = i;
                stopwatch.Stop();
                Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds);

                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    counter = i;
                stopwatch.Stop();
                Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds);
            }

            Console.ReadKey();
        }
    }
}

出力:

With lock: 2013
Without lock: 211
With lock: 2002
Without lock: 210
With lock: 1989
Without lock: 210
With lock: 1987
Without lock: 207
With lock: 1988
Without lock: 208

4
これは悪い例かもしれません。ループは実際には何も実行せず、単一の変数割り当てとロックは少なくとも2つの関数呼び出しです。また、取得しているロックごとに20nsはそれほど悪くありません。
Zar Shardan 2017年

5

「コスト」を定義する方法はいくつかあります。ロックの取得と解放には実際のオーバーヘッドがあります。ジェイクが書いているように、この操作が何百万回も実行されない限り、それは無視できます。

より適切なのは、これが実行のフローに及ぼす影響です。このコードは、一度に1つのスレッドしか入力できません。この操作を定期的に実行するスレッドが5つある場合、そのうち4つはロックが解放されるのを待ち、そのロックが解放された後、そのコードに入る予定の最初のスレッドになります。したがって、アルゴリズムは大幅に影響を受けます。その程度は、アルゴリズムと操作が呼び出される頻度に依存します。競合状態を導入せずにそれを回避することはできませんが、ロックされたコードへの呼び出しの数を最小限に抑えることで改善できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.