ロックされていないミューテックスのロックはどの程度効率的ですか?ミューテックスのコストはいくらですか?


149

低レベル言語(C、C ++など):ミューテックスの束(pthreadが提供するものやネイティブシステムライブラリが提供するものなど)とオブジェクトの単一のミューテックスのどちらを使用するかを選択できます。

ミューテックスをロックすることはどのくらい効率的ですか?つまり、アセンブラー命令はいくつありますか。また、それらにかかる時間(ミューテックスがロック解除されている場合)はどれくらいかかりますか?

mutexの費用はいくらですか?ミューテックスを本当にたくさん持つのは問題ですか?または、私がint変数を持っているのと同じくらい多くのミューテックス変数をコードにスローすることはできますか?

(ハードウェアの違いはどれくらいかわかりません。ある場合は、それについても知りたいのですが、ほとんどの場合、共通のハードウェアに興味があります。)

重要なのは、オブジェクト全体に対して単一のミューテックスではなく、オブジェクトの一部のみをカバーする多くのミューテックスを使用することで、多くのブロックを安全にできることです。そして、これについてどこまで行けばいいのかと思っています。つまり、どれほど複雑で、これが何個のミューテックスを意味するかに関係なく、可能なブロックを可能な限り安全に保護する必要がありますか?


ロックに関するWebKitsブログ投稿(2016)は、この質問に非常に関連しており、スピンロック、アダプティブロック、futexなどの違いについて説明しています。


これは、実装およびアーキテクチャ固有のものになります。ネイティブハードウェアサポートがある場合、ミューテックスによってはほとんどコストがかかりません。これ以上の情報なしに答えることは不可能です。
Gian

2
@Gian:ええ、もちろん、私は私の質問にこのサブ質問を示唆しています。一般的なハードウェアについて知りたいのですが、例外がある場合は注目に値します。
アルバート

その意味合いはどこにも本当にありません。あなたは「アセンブラー命令」について質問します-答えは、あなたが話しているアーキテクチャーに応じて、1命令から1万命令までのいずれかになります。
ジャン

15
@Gian:次に、この答えを正確に教えてください。それが実際にx86とamd64で何であるかを言ってください、それが1つの命令であるアーキテクチャの例を与え、それが10kであるものを与えてください。私の質問からそれを知りたいのは明らかではありませんか?
アルバート

回答:


120

ミューテックスの束を持っているか、オブジェクト用のミューテックスを1つ持っているかのどちらかを選択できます。

多くのスレッドがあり、オブジェクトへのアクセスが頻繁に発生する場合、複数のロックにより並列処理が増加します。ロックが多いほどロックのデバッグが多くなるため、保守性は犠牲になります。

ミューテックスをロックすることはどのくらい効率的ですか?つまり、アセンブラー命令はどれくらいありそうで、それらにかかる時間はどれくらいですか(ミューテックスがロック解除されている場合)?

正確なアセンブラ命令は、の少なくともオーバーヘッドいるミューテックス - メモリ/キャッシュ・コヒーレンシの保証は主なオーバーヘッドです。そして、特定のロックが取得される頻度は低くなります-優れています。

ミューテックスは2つの主要な部分(単純化)で構成されています。(1)ミューテックスがロックされているかどうかを示すフラグと、(2)待機キューです。

フラグの変更はほんの少しの指示であり、通常はシステムコールなしで行われます。mutexがロックされている場合、syscallは呼び出しスレッドを待機キューに追加し、待機を開始します。ロック解除は、待機キューが空の場合は安価ですが、待機しているプロセスの1つを起動するためにシステムコールが必要です。(一部のシステムでは、mutexの実装に安価/高速のsyscallが使用され、競合が発生した場合にのみ低速(通常)のシステムコールになります。)

ロック解除されたミューテックスのロックは、非常に安価です。競合なしのミューテックスのロック解除も安価です。

mutexの費用はいくらですか?ミューテックスを本当にたくさん持つのは問題ですか?または、int変数と同じくらい多くのミューテックス変数をコードにスローすることはできますか?

必要なだけmutex変数をコードにスローできます。アプリケーションが割り当てることができるメモリの量によってのみ制限されます。

概要。ユーザースペースのロック(および特にmutex)は安価で、システムの制限を受けません。しかし、それらの多くはデバッグにとって悪夢です。シンプルなテーブル:

  1. ロックが少ないほど、競合が多くなり(syscallが遅くなり、CPUがストールする)、並列処理が少なくなる
  2. ロックが少ないほど、マルチスレッドの問題をデバッグする際の問題が少なくなります。
  3. ロックが多いほど、競合が少なくなり、並列性が高くなります
  4. ロックが多いほど、不可解なデッドロックに遭遇する可能性が高くなります。

アプリケーションのバランスのとれたロックスキームを見つけて維持する必要があります。通常は#2と#3のバランスを取ります。


(*)あまり頻繁にロックされないmutexの問題は、アプリケーションでロックが多すぎると、CPU /コア間のトラフィックの多くが他のCPUのデータキャッシュからmutexメモリをフラッシュして、キャッシュの一貫性。キャッシュフラッシュは軽量の割り込みのようであり、CPUによって透過的に処理されますが、いわゆるストール(「ストール」の検索)が導入されます。

そして、ストールが原因でロックコードの実行が遅くなります。多くの場合、アプリケーションが遅い理由が明確に示されていません。(一部のアーチはCPU /コア間のトラフィック統計を提供しますが、一部は提供しません。)

問題を回避するために、人々は一般にロックの競合の可能性を減らし、ストールを回避するために多数のロックに訴えます。これが、システム制限を受けない安価なユーザースペースロックが存在する理由です。


ありがとう、それはほとんど私の質問に答えます。カーネル(Linuxカーネルなど)がmutexを処理し、syscallsを介してそれらを制御することを知りませんでした。しかし、Linux自体がスケジューリングとコンテキストスイッチを管理するので、これは理にかなっています。しかし今、私はミューテックスのロック/ロック解除が内部で何をするかについて大まかな想像力を持っています。
アルバート

2
@アルバート:ああ。コンテキストスイッチを忘れてしまった...コンテキストスイッチのパフォーマンスが低すぎます。ロックの取得に失敗してスレッドが待機しなければならない場合、それはコンテキスト切り替えの半分に過ぎません。CS自体は高速ですが、CPUが他のプロセスによって使用される可能性があるため、キャッシュはエイリアンデータで満たされます。スレッドが最終的にロックを取得した後、CPUがRAMからほとんどすべてを新たに再ロードする必要がある可能性があります。
Dummy00001

@ Dummy00001別のプロセスに切り替えると、CPUのメモリマッピングを変更する必要があります。それはそれほど安くはありません。
curiousguy

27

同じことを知りたくて測定しました。私のボックス(AMD FX(tm)-8150 Eight-Core Processor at 3.612361 GHz)では、独自のキャッシュラインにあり、すでにキャッシュされているロック解除されたミューテックスをロックおよびロック解除すると、47クロック(13 ns)かかります。

2つのコア間の同期(CPU#0と#1を使用)のため、2つのスレッドで102 nsごとに1回しかロック/ロック解除ペアを呼び出せなかったため、51 nsごとに1回しか呼び出せず、およそ38 nsは、スレッドがロック解除を行った後、次のスレッドが再びロックする前に回復します。

これを調査するために使用したプログラムは、次の場所にあります。 https //github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxxにあります。

ボックスに固有のハードコードされた値(xrange、yrange、rdtscオーバーヘッド)がいくつかあることに注意してください。そのため、機能する前に、実験する必要があるでしょう。

その状態で生成されるグラフは次のとおりです。

ここに画像の説明を入力してください

これは、次のコードでベンチマークを実行した結果を示しています。

uint64_t do_Ndec(int thread, int loop_count)
{
  uint64_t start;
  uint64_t end;
  int __d0;

  asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
  mutex.lock();
  mutex.unlock();
  asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
  asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
  return end - start;
}

2つのrdtsc呼び出しは、「ミューテックス」をロックおよびロック解除するために必要なクロック数を測定します(私のボックスのrdtsc呼び出しには39クロックのオーバーヘッドがあります)。3番目のasmは遅延ループです。遅延ループのサイズは、スレッド0の場合よりスレッド1の場合のほうが1カウント小さいため、スレッド1の方がわずかに高速です。

上記の関数は、サイズが100,000のタイトループで呼び出されます。スレッド1の方が関数は少し高速ですが、ミューテックスの呼び出しのため、両方のループが同期します。これは、ロック/ロック解除のペアで測定されたクロック数がスレッド1の方がわずかに多いため、その下のループの遅延が短いことを考慮して、グラフで確認できます。

上記のグラフでは、右下のポイントは遅延loop_countが150の測定値であり、次に下のポイントを左に向かって、loop_countは測定ごとに1つずつ減少します。77になると、両方のスレッドで102 nsごとに関数が呼び出されます。その後、loop_countがさらに減少すると、スレッドを同期することができなくなり、ほとんどの場合、mutexが実際にロックされ始め、ロック/ロック解除に必要なクロック量が増加します。このため、関数呼び出しの平均時間も増加します。プロットポイントが上昇し、右方向に戻ります。

これから、50 nsごとのミューテックスのロックとロック解除は私のボックスでは問題ではないと結論付けることができます。

結局のところ、私の結論は、OPの質問に対する答えは、ミューテックスを追加することが競合が少なくなる限り、より良いということです。

mutexはできるだけ短くロックしてください。それらをループの外に置く唯一の理由は、そのループが100 ns(つまり、そのループを同時に50 nsで実行したいスレッドの数)または13 nsの時間よりも速くループする場合です。ループサイズは、競合によって発生する遅延よりも遅延が大きくなります。

編集:私は今この主題についてもっと知識を得て、ここで提示した結論を疑い始めました。まず第一に、CPU 0と1はハイパースレッド化されています。AMDは8つの実際のコアがあると主張していますが、他の2つのコア間の遅延がはるかに大きいため(つまり、2と3、4と5、6と7のように、0と1はペアを形成するため) )。次に、std :: mutexは、mutexのロックをすぐに取得できない場合にシステムコールを実際に実行する前に少しロックをスピンするように実装されています(間違いなく非常に遅くなります)。したがって、ここで測定したのは、最も理想的な状況であり、実際のロックとロック解除は、ロック/ロック解除ごとに大幅に時間がかかる可能性があります。

結論として、ミューテックスはアトミックで実装されています。コア間でアトミックを同期するには、内部バスをロックする必要があります。これにより、対応するキャッシュラインが数百クロックサイクルの間フリーズします。ロックを取得できない場合は、システムコールを実行してスレッドをスリープ状態にする必要があります。これは明らかに非常に低速です(システムコールは10ミリ秒のオーダーです)。スレッドはとにかくスリープしなければならないので、通常、それは実際には問題ではありませんが、スレッドが通常スピンしている間ロックを取得できず、システムコールを実行できない高競合の問題である可能性があります。その後すぐにロックをかけてください。たとえば、複数のスレッドがタイトなループでミューテックスをロックおよびロック解除し、それぞれが1マイクロ秒程度ロックを保持する場合、次に、彼らは常にスリープ状態に置かれ、再び目覚めるという事実によって、非常に遅くなる可能性があります。また、スレッドがスリープ状態になり、別のスレッドがウェイクアップする必要がある場合、そのスレッドはシステムコールを実行する必要があり、約10マイクロ秒遅延します。したがって、この遅延は、(スピンに時間がかかりすぎた後)別のスレッドがカーネルでそのミューテックスを待機しているときにミューテックスをロック解除しているときに発生します。


10

これは、実際に「ミューテックス」と呼ばれるもの、OSモードなどによって異なります。

で、最小は連動メモリ操作のコストです。これは比較的重い操作です(他のプリミティブアセンブラコマンドと比較して)。

ただし、それは非常に高くなる可能性があります。カーネルオブジェクト(つまり、OSが管理するオブジェクト)を「ミューテックス」と呼び、ユーザーモードで実行する場合、そのオブジェクトでのすべての操作はカーネルモードトランザクションにつながり、非常に重いトランザクションになります。

たとえば、Intel Core Duoプロセッサ、Windows XP。連動操作:約40 CPUサイクルかかります。カーネルモードコール(システムコール)-約2000 CPUサイクル。

この場合は、クリティカルセクションの使用を検討してください。これは、カーネルミューテックスとインターロックされたメモリアクセスのハイブリッドです。


7
Windowsのクリティカルセクションは、ミューテックスに非常に近いです。それらは通常のmutexセマンティクスを持っていますが、プロセスローカルです。最後の部分は、プロセス内で完全に処理できるため(つまり、ユーザーモードコード)、それらを大幅に高速化します。
MSalters

2
一般的な演算のCPUサイクルの量(たとえば、算術/ if-else / cache-miss / indirection)も比較のために提供されている場合、この数はより有用です。....数の参照があればそれも素晴らしいです。インターネットでは、そのような情報を見つけることは非常に困難です。
javaLover 2017年

@javaLoverオペレーションはサイクルで実行されません。これらは、いくつかのサイクルの間、算術演算装置で実行されます。それは非常に異なります。時間内の任意の命令のコストは、定義された数量ではなく、リソース使用のコストのみです。これらのリソースは共有されます。メモリ命令の影響は、多くのキャッシングなどに依存します
curiousguy

@curiousguy同意する。はっきりしませんでした。std::mutex平均して使用時間(秒)を10倍以上にするなどの回答をお願いしint++ます。しかし、それは多くのことに大きく依存しているので、答えるのが難しいことを知っています。
javaLover

6

コストは実装によって異なりますが、次の2つの点に注意してください。

  • それはかなり原始的な操作であり、その使用パターン(多く使用される)により可能な限り最適化されるため、コストはおそらく最小です)。
  • 安全なマルチスレッド操作が必要な場合はそれを使用する必要があるため、それがどれほど高価であってもかまいません。あなたがそれを必要とするなら、あなたはそれを必要とします。

シングルプロセッサシステムでは、データをアトミックに変更するのに十分な長さの割り込みを無効にできます。マルチプロセッサシステムでは、テストと設定の戦略を使用できます。

どちらの場合も、指示は比較的効率的です。

大規模なデータ構造に単一のミューテックスを提供する必要があるのか​​、それとも各セクションに1つずつ、多数のミューテックスを持つ必要があるのか​​は、バランスをとる行為です。

単一のミューテックスを使用することにより、複数のスレッド間の競合のリスクが高くなります。セクションごとにミューテックスを設定することでこのリスクを減らすことができますが、スレッドがその仕事をするために180のミューテックスをロックしなければならない状況にはなりたくありません:-)


1
ええ、でもどのくらい効率的ですか?単一の機械語命令ですか?または約10?または約100ですか?1000?もっと?これらはすべて効率的ですが、極端な状況では違いが生じる可能性があります。
アルバート

1
まあ、それは完全に実装に依存します。割り込みをオフにし、整数をテスト/設定し、ループ内の割り込みを約6つの機械語命令で再アクティブ化できます。プロセッサーはそれを単一の命令として提供する傾向があるので、テストと設定はおよそ同じくらい多くで行うことができます。
paxdiablo

バスロックテストとセットは、x86の単一(かなり長い)命令です。それを使用する残りの機構はかなり高速です(「テストが成功したかどうか」は、CPUが高速で実行できるという問題です)が、物事をブロックする部分であるため、重要なのはバスロックされた命令の長さです。割り込みのあるソリューションは、操作がOSカーネルに限定されているため、簡単なDoS攻撃を防ぐことができます。
ドナルフェロー

ところで、他の人にスレッドを譲る手段としてドロップ/再取得を使用しないでください。それはマルチコアシステムを吸い込む戦略です。(これは、CPythonが間違ってしまうことの比較的少ないものの1つです。)
Donal Fellows

@Donal:ドロップ/再取得とはどういう意味ですか?それは重要に聞こえます。詳細について教えてください。
アルバート

5

私はpthreadとmutexはまったくの初心者ですが、実験を行って、競合がない場合のmutexのロック/ロック解除のコストはほとんど変わりませんが、競合がある場合は、ブロッキングのコストが非常に高くなることを確認できます。スレッドプールを使用して、ミューテックスロックで保護されたグローバル変数の合計を計算するだけの簡単なコードを実行しました。

y = exp(-j*0.0001);
pthread_mutex_lock(&lock);
x += y ;
pthread_mutex_unlock(&lock);

1つのスレッドで、プログラムは10,000,000の値をほぼ瞬時(1秒未満)に合計します。2つのスレッド(4コアのMacBook)では、同じプログラムに39秒かかります。

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