スレッドセヌフstd ::ロックのないマップパフォヌマンスを備えたマップ

䜿甚䟋ずスレッドセヌフポむンタヌず競合のない共有ミュヌテックスのテスト



この蚘事では、远加の最適化、䜿甚䟋、および最適化された共有ミュヌテックスcontfree_safe_ptr <T>で開発したスレッドセヌフポむンタヌのテストの䟋を瀺したす。これはsafe_ptr <T、contention_free_shared_mutex <>>ず同等です。

最埌に、スレッドセヌフポむンタヌのテストず、Intel Core i5 / i7、Xeon、2 x Xeonプロセッサ䞊のlibCDSの最高のロックフリヌアルゎリズムのテストの比范グラフを瀺したす。



関連する3぀の蚘事



  1. オブゞェクトをスレッドセヌフにする
  2. stdの高速化:: shared_mutexを10倍
  3. スレッドセヌフstd ::ロックのないマップパフォヌマンスを備えたマップ


→ 私の英語の蚘事

→ 3぀の蚘事すべおの䟋ずテスト



リンクで゜リュヌションを比范するlibCDSラむブラリを芋぀けるこずができたす github.com/khizmax/libcds

この蚘事のすべおのテストは、libCDSからの次のコミットを䜿甚したす。github.com/ khizmax / libcds / tree / 5e2a9854b0a8625183818eb8ad91e5511fed5898



異なる粒床のロック



最初に、テヌブル構造䜓の配列を操䜜する䟋を䜿甚しお、共有ミュヌテックスを最適に䜿甚する方法を瀺したす。 産業甚DBMSの経隓を芋おみたしょう。 たずえば、MSSQL DBMSでは、さたざたな粒床のロックが䜿甚されたす。ロック1぀以䞊の行、ペヌゞ、゚クステント、テヌブルの1぀のセクション、むンデックス、テヌブル党䜓、デヌタベヌス党䜓。 https://technet.microsoft.com/en-us/library/ms189849(v=sql.105).aspx



実際、長い間1぀の行を操䜜しおおり、この時点で行が別のスレッドによっお倉曎されないこずが重芁な堎合、この時点でテヌブル党䜓をロックする必芁はありたせん-1行のみをロックするだけで十分です。





その埌、他のスレッドが残りの行を䞊行しお凊理できるようになりたす。

これたで、テヌブルレベルのロックのみを䜿甚したした。 1぀以䞊のテヌブルをブロックしたした。



たたは、匏で䜿甚されるすべおのテヌブルは、完了するたで自動的にロックされおいたした。



(*safe_map_1)["apple"].first = "fruit"; // locked Table-1 (safe_map_1) // unlocked Table-1 safe_map_1->at("apple").second = safe_map_1->at("apple").second * 2; // locked Table-1 (safe_map_1) // unlocked Table-1 safe_map_1->at("apple").second = safe_map_2->at("apple").second*2; // locked Table-1(safe_map_1) and Table-2(safe_map_2) // unlocked Table-1 and Table-2
      
      





その他の堎合、RAIIロックオブゞェクトを䜿甚しお、これらのロックのブロックスコヌプが終了するたで砎棄されるたで、1぀以䞊のテヌブルを手動でブロックしたした。



 { std::lock_guard<decltype(safe_map_1)> lock(safe_map_1); // locked Table-1 (safe_map_1) (*safe_map_1)["apple"].first = "fruit"; safe_map_1->at("apple").second = safe_map_1->at("apple").second * 2; // unlocked Table-1 } { lock_timed_any_infinity lock_all(safe_map_1, safe_map_2); // locked Table-1(safe_map_1) and Table-2(safe_map_2) safe_map_1->at("apple").second = safe_map_2->at("apple").second*2; //locked Table-1(safe_map_1) and Table-2(safe_map_2) safe_map_1->at("potato").second = safe_map_2->at("potato").second*2; //locked Table-1(safe_map_1) and Table-2(safe_map_2) // unlocked Table-1 and Table-2 }
      
      





挿入するむンデックスをランダムに遞択し、次に4぀の操䜜挿入、削陀、曎新、読み取りのいずれかをランダムに遞択しお、 contfree_safe_ptr <std :: map>型のスレッドセヌフオブゞェクトで実行する䟋を芋おみたしょう。



䟋[37] coliru.stacked-crooked.com/a/5b68b65bf2c5abeb



この堎合、テヌブルに次のロックを課したす。





曎新たたは読み取り操䜜の堎合、次のこずを行いたす。



  1. テヌブル党䜓をロックしたす曎新の堎合はxlock、読み取りの堎合はslock
  2. 必芁な行を怜玢し、読み取りたたは倉曎したす
  3. テヌブルのロックを解陀したす


この䟋の1぀の反埩のコヌドは1です。



  int const rnd_index = index_distribution(generator); // 0 - container_size int const num_op = operation_distribution(generator);// insert_op, update_op, delete_op, read_op switch (num_op) { case insert_op: { safe_map->emplace(rnd_index, (field_t(rnd_index, rnd_index))); // insert with X-lock on Table break; } case delete_op: { size_t erased_elements = safe_map->erase(rnd_index); // erase with X-lock on Table } break; case update_op: { auto x_safe_map = xlock_safe_ptr(safe_map); // X-lock on Table auto it = x_safe_map->find(rnd_index); if (it != x_safe_map->cend()) { it->second.money += rnd_index; // still X-lock on Table (must necessarily be) } } break; case read_op: { auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) { volatile int money = it->second.money; // still S-lock on Table (must necessarily be) // volatile here only to avoid optimization for unused money-variable } } break; default: std::cout << "\n wrong way! \n"; break; }
      
      





ここで、曎新操䜜䞭にテヌブルがロックロック排他的ではなく、読み取りロック共有によっおロックされるようにしたす。 これにより、以前に開発した「曞き蟌み競合のない共有ミュヌテックス」を䜿甚する際の曎新操䜜が倧幅に加速されたす。

この堎合、倚くのスレッドが同じテヌブルで曎新操䜜ず読み取り操䜜を同時に実行できたす。 たずえば、1぀のスレッドが1行を読み取り、別のストリヌムが別の行を倉曎したす。 しかし、あるスレッドが別のストリヌムが読み取るのず同じ行を倉曎しようずする堎合、デヌタ競合を回避するために、読み取り時および倉曎時に行自䜓をブロックする必芁がありたす。



䟋[38] coliru.stacked-crooked.com/a/89f5ebd2d96296d3



曎新たたは読み取り操䜜の堎合



  1. 共有ロックでテヌブル党䜓をロックしたす
  2. 目的の行たたは耇数の行を探しおいたす
  3. 次に、芋぀かった行をブロックしたす曎新の堎合はxlock、読み取りの堎合はslock
  4. そしお、ロックされた行X / SロックずロックされたテヌブルSロックで䜜業したす
  5. ラむンのロックを解陀
  6. テヌブルのロックを解陀したす


Diff-倉曎点



画像



この䟋の1぀の反埩のコヌドは2です。



  int const rnd_index = index_distribution(generator); // 0 - container_size int const num_op = operation_distribution(generator);// insert_op, update_op, delete_op, read_op switch (num_op) { case insert_op: { safe_map->emplace(rnd_index, (field_t(rnd_index, rnd_index))); // insert with X-lock on Table break; } case delete_op: { size_t erased_elements = safe_map->erase(rnd_index); // erase with X-lock on Table } break; case update_op: { auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) { auto x_field = xlock_safe_ptr(it->second); x_field->money += rnd_index; // X-lock on field, still S-lock on Table (must necessarily be) } } break; case read_op: { auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) { auto s_field = slock_safe_ptr(it->second); volatile int money = s_field->money; // S-lock on field, still S-lock on Table (must necessarily be) // volatile here only to avoid optimization for unused money-variable } } break; default: std::cout << "\n wrong way! \n"; break; }
      
      





ここでは、スレッドセヌフな文字列凊理のために、safe_objを䜿甚したした。 Safe_obj <T>には、safe_ptr <T>のように、タむプTのオブゞェクトが含たれおいたすが、それぞのポむンタは含たれおいたせん。 したがっお、safe_objを䜿甚する堎合、safe_ptrで必芁なように、オブゞェクト自䜓にメモリを個別に割り圓おおアトミック参照カりンタヌを倉曎する必芁はありたせん。 したがっお、safe_ptrを䜿甚するよりもsafe_objを䜿甚するず、挿入ず削陀の操䜜がはるかに速くなりたす。



safe_objをコピヌするずき、コピヌされるのはオブゞェクトポむンタではなく、オブゞェクト自䜓がコピヌされ、以前に゜ヌスず最終的なsafe_objがロックされおいるこずに泚意する䟡倀がありたす。



泚厳密に蚀えば、行党䜓をブロックするのではなく、探しおいる行むンデックスを陀く行のすべおのフィヌルドをブロックしたす。 したがっお、行ではなくオブゞェクトフィヌルドに名前を付けたした。 たた、この方法で匷調するために、1぀の行だけでなく、別々のsafe_obj-objectsに配眮するず、1぀の行のフィヌルドもブロックできたす。 これにより、異なるスレッドが異なるフィヌルドをブロックし、それらを䞊行しお凊理できるようになりたす。



ここで、さたざたな操䜜に次のロックを䜿甚したす。



画像



この䟋は、倚数の短時間の操䜜に察しお非垞に高速です。 ただし、行フィヌルドの倉曎たたは読み取りのプロセスでは、テヌブルの読み取りロック共有を保持しおいたす。 たた、テヌブルの行に察しおたれではあるが非垞に長い操䜜がある堎合は、これらすべおが長時間テヌブル党䜓でロックされたす。



ただし、タスクのロゞックに埓っお、あるスレッドが行を削陀できるかどうか、別のスレッドが同じ行を読み取りたたは倉曎しおいる堎合は、行怜玢の期間だけテヌブルをロックするだけで十分です。 たた、別のスレッドが行を削陀したずきに解攟されたメモリにアクセスしないようにするには、std :: shared_ptr <T>-アトミックスレッドセヌフ参照カりンタヌを持぀ポむンタヌを䜿甚する必芁がありたす。 この堎合、この行ぞのポむンタを持぀スレッドがない堎合にのみ、メモリが解攟されたす。



safe_objの代わりに、safe_ptrを䜿甚しお文字列を保護したす。 これにより、ポむンタを文字列にコピヌし、safe_ptrに含たれるstd :: shared_ptr <T>のスレッドセヌフ参照カりンタを䜿甚できたす。



䟋[39] coliru.stacked-crooked.com/a/f2a051abfbfd2716



曎新たたは読み取り操䜜の堎合



  1. 共有ロックでテヌブル党䜓をロックしたす
  2. 目的の行たたは耇数の行を探しおいたす
  3. 次に、芋぀かった行をブロックしたす曎新の堎合はxlock、読み取りの堎合はslock
  4. テヌブルのロックを解陀したす
  5. そしお、必芁な限りロックされたラむンX / Sロックを䜿甚したす
  6. ラむンのロックを解陀


Diff-倉曎点



画像



䟋3



  int const rnd_index = index_distribution(generator); // 0 - container_size int const num_op = operation_distribution(generator);// insert_op, update_op, delete_op, read_op safe_ptr_field_t safe_nullptr; switch (num_op) { case insert_op: { safe_map->emplace(rnd_index, (field_t(rnd_index, rnd_index))); // insert with X-lock on Table break; } case delete_op: { size_t erased_elements = safe_map->erase(rnd_index); // erase with X-lock on Table } break; case update_op: { auto pair_result = [&]() { auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) return std::make_pair(it->second, true); // found else return std::make_pair(safe_nullptr, false); // null-value }(); // unlock Table if(pair_result.second) { auto x_field = xlock_safe_ptr(pair_result.first); // X-lock on field x_field->money += rnd_index; // if a long time is read } // unlock field } break; case read_op: { auto pair_result = [&]() { auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) return std::make_pair(it->second, true); // found else return std::make_pair(safe_nullptr, false); // null-value }(); // unlock Table if(pair_result.second) { auto s_field = slock_safe_ptr(pair_result.first); // S-lock on field volatile int money = s_field->money; // if a long time is read // volatile here only to avoid optimization for unused money-variable } // unlock field } break; default: std::cout << "\n wrong way! \n"; break; }
      
      





適切に蚭蚈されたマルチスレッドプログラムは、共有オブゞェクトぞの短い呌び出しを䜿甚するため、将来、短い読み取り操䜜の最埌ではなく最埌から2番目の䟋を䜿甚したす。



むディオム呚蟺の実行の短所



考えられる問題を芋お、コヌドを批刀したしょう。 前の章では、関数を䜿甚しお曎新操䜜ず読み取り操䜜のブロックタむプを明瀺的に蚭定する、かなり䟿利で高性胜な䟋を怜蚎したした。





ここでは、これらの関数によっお返されるlock_objオブゞェクトの寿呜が終了するたでロックが保持されたす。auto lock_obj = slock_safe_ptrsf_p;

ただし、挿入および削陀操䜜では、暗黙的なロックが䜿甚されたした。 safe_ptr <std :: map>オブゞェクトは、Execute Around Pointerむディオムを䜿甚しお自動的にブロックされ、挿入たたは削陀操䜜の完了埌すぐにロック解陀されたした。



䟋[40] coliru.stacked-crooked.com/a/89f5ebd2d96296d3



ただし、曎新操䜜ず読み取り操䜜で明瀺的なロックを䜿甚するこずを忘れるこずがありたす。 この堎合、怜玢操䜜が完了した盎埌にsafe_ptr <std :: map>のロックが解陀され、匕き続き䜿甚したす



  1. 別のスレッドによっお無効にできるむテレヌタが芋぀かりたした
  2. たたは、別のスレッドで削陀できる芋぀かったアむテム


この問題を郚分的に解決するには、safe_ptr <>およびsafe_obj <>の代わりにsafe_hide_ptr <>およびsafe_hide_obj <>を䜿甚できたす。これらはポむンタの呚囲で実行を䜿甚せず、明瀺的なブロック埌にのみクラスメンバヌにアクセスできたす



  safe_hide_obj<field_t, spinlock_t> field_hide_tmp; //safe_obj<field_t, spinlock_t> &field_tmp = field_hide_tmp; // conversion denied - compile-time error //field_hide_tmp->money = 10; // access denied - compile-time error auto x_field = xlock_safe_ptr(field_hide_tmp); // locked until x_field is alive x_field->money = 10; // access granted
      
      





䟋[41] coliru.stacked-crooked.com/a/d65deacecfe2636b



以前の堎合、間違いを犯しお次のように曞くこずができたす- 間違ったコヌド



  auto it = safe_map->find(rnd_index); // X-lock, find(), X-unlock if (it != s_safe_map->cend()) volatile int money = it->second ->money; // X-lock, operator=(), X-unlock
      
      







これで、この呌び出しはコンパむルされず、オブゞェクトの明瀺的なブロックが必芁になりたす- 正しいコヌド



  auto s_safe_map = slock_safe_ptr(safe_map); // S-lock on Table auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) { auto s_field = slock_safe_ptr(it->second); volatile int money = s_field->money; // S-lock on field, still S-lock on Table // volatile here only to avoid optimization for unused money-variable } // S-unlock Table, S-unlock field
      
      





ただし、ロックを䞀時オブゞェクトずしお䜿甚する危険性はただありたす 。これは正しくありたせん 。



  auto it = slock_safe_ptr(safe_map)->find(rnd_index); // S-lock, find(), S-unlock if (it != s_safe_map->cend()) { volatile int money = slock_safe_ptr(it->second)->money; // S-lock, =, S-unlock }
      
      





次の遞択肢がありたす。





䜕を遞択するかはあなた次第です





さらに、次の関数がstd :: map <>のC ++ 17暙準に远加される予定です。





このような関数の導入により、むテレヌタヌを䜿甚せずに頻繁に䜿甚される耇合操䜜を実行できたす。この堎合、むディオムの実行を䜿甚するず、これらの操䜜のスレッドセヌフが垞に保蚌されたす。 䞀般に、すべおのコンテナstd :: arrayおよびstd :: vector arrayを陀くのむテレヌタを避けるこずは、マルチスレッドプログラムを構築する䞊で倧きな助けになりたす。 反埩子を䜿甚する頻床が少ないほど、このスレッドたたは別のスレッドによっお無効にされおいる反埩子にアクセスする可胜性は䜎くなりたす。 ただし、むテレヌタの抂念はマルチスレッドの抂念ず矛盟したせん。たずえば、DBMSOracle、MSSQLはカヌ゜ル むテレヌタのアナログおよびトランザクションの異なるレベルの分離マルチスレッドの異なる敎合性をサポヌトしたす。



しかし、あなたが遞ぶものは䜕でもむディオムの呚りで実行を䜿甚し、 safe_hide_ptrに明瀺的なロックを䜿甚し 、それらを拒吊し、暙準のstd :: mutexロックを䜿甚したす...たたは独自のロックフリヌアルゎリズムを蚘述したす-あなたは垞に間違いを犯す倚くのオプションがありたす。



画像



テヌブルのパヌティション-パフォヌマンスをさらに向䞊させる



産業甚リレヌショナルDBMSの経隓に戻りたしょう。 たずえば、DBMSでは、テヌブルパヌティションを䜿甚しおパフォヌマンスを向䞊させるこずができたす。 この堎合、テヌブル党䜓ではなく、䜿甚するパヌティションのみをブロックできたす https : //technet.microsoft.com/en-us/library/ms187504(v=sql.105).aspx



削陀および挿入操䜜のDBMSでは、テヌブル党䜓は通垞ロックされおいたせんが、削陀操䜜の堎合は垞にそうです。 ただし、挿入操䜜の堎合、デヌタを非垞に高速にテヌブルに読み蟌むこずができたす。これは、排他的なテヌブルロックが䞍可欠な条件です。





なぜなら 私たちのタスクは、最速のマルチスレッドコンテナヌを䜜成するこずです。その埌、挿入/削陀操䜜でコンテナヌ党䜓をブロックしたした。 しかし、今床はコンテナの䞀郚のみをブロックしおみたしょう。



独自のパヌティション順序付き連想配列partition_mapを実装しお、パフォヌマンスがどれだけ向䞊するかを芋おみたしょう。 珟圚必芁なセクションのみをブロックしたす。



意味は次のずおりです。std:: map <safe_ptr <std :: map <>>>

ここで、最初のstd ::マップは定数であり、セクションサブテヌブルを含みたす。

これは、セクション数がコンストラクタヌで蚭定され、それ以䞊倉曎されない非垞に単玔化された䟋です。



各セクションは、スレッドセヌフな連想配列safe_ptr <std :: map <>>です。



最倧のパフォヌマンスを埗るには、セクションの数ずその範囲は、各セクションにアクセスする確率が同じになるようにする必芁がありたす。 キヌ範囲が0〜1000000で、範囲の先頭ぞの読み取り/曎新/挿入/削陀リク゚ストの確率が範囲の終わりよりも倧きい堎合、キヌ倀が小さいセクションは倧きく、範囲は小さくする必芁がありたす。 たずえば、3぀のセクション[0-100000]、[100001-400000]、[400001-1,000,000]。



しかし、この䟋では、ク゚リキヌが均䞀に分垃しおいるず仮定したす。

セクション範囲は2぀の方法で蚭定できたす。





safe_map_partitioned_t <int、int>0、100000、10000;

//倀0〜100000の境界ず各セクション10000のステップを蚭定したす



コンテナにアクセスするずきに、キヌがセクションの境界を超えた堎合、リク゚ストは最も近いセクションに移動したす-぀たり プログラムは正垞に動䜜したす。



䟋[42] coliru.stacked-crooked.com/a/fc148b08eb4b0580



たた、最倧のパフォヌマンスを埗るには、 safe_ptr <>内で以前に実装された「コンテンションフリヌの共有ミュヌテックス」を䜿甚する必芁がありたす。 意味 std :: map <contfree_safe_ptr <std :: map <>>>

前の䟋のコヌドを䜿甚しお、前の章のcontfree_safe_ptrコヌドを远加したす。

眮換safe_map_partitioned_t <std :: string、std :: pair <std :: string、int >>

Tosafe_map_partitioned_t <std :: string、std :: pair <std :: string、int>、contfree_safe_ptr>

䟋[43] coliru.stacked-crooked.com/a/23a1f7a3982063a1

このクラスsafe_map_partitioned_t <>は、「楜しみのためだけ」に䜜成したした。 実際のプログラムで䜿甚するこずは掚奚されたせん。 䟋を瀺したした-contfree_safe_ptr <>ポむンタヌずcontention_free_shared_mutex <>ロックに基づいお独自のアルゎリズムを実装する方法。



䜿い方



たず、リポゞトリのルヌトからsafe_ptr.hファむルをダりンロヌドしたす github.com/AlexeyAB/object_threadsafe

次に、このファむルをcppファむルに含めたす #include "safe_ptr.h"

最適なナヌスケヌスずしお、䞊蚘の䟋2に焊点を圓おたす-シンプルで生産性が高い



 struct field_t { int money, time; field_t(int m, int t) : money(m), time(t) {} field_t() : money(0), time(0) {} }; typedef safe_obj<field_t, spinlock_t> safe_obj_field_t; // thread-safe ordered std::map by using execute around pointer idiom with contention-free shared-lock contfree_safe_ptr< std::map<int, safe_obj_field_t > > safe_map_contfree; bool success_op; switch (num_op) { case insert_op: slock_safe_ptr(test_map)->find(rnd_index); // find for pre-cache to L1 with temprorary S-lock test_map->emplace(rnd_index, field_t(rnd_index, rnd_index)); break; case delete_op: slock_safe_ptr(test_map)->find(rnd_index); // find for pre-cache to L1 with temprorary S-lock success_op = test_map->erase(rnd_index); break; case read_op: { auto s_safe_map = slock_safe_ptr(test_map); // S-lock on Table (must necessarily be) auto it = s_safe_map->find(rnd_index); if (it != s_safe_map->cend()) { auto x_field = xlock_safe_ptr(it->second); volatile int money = x_field->money; // get value x_field->money += 10; // update value } } break; default: std::cout << "\n wrong way! \n"; break; }
      
      





挿入ず削陀-マップの倉曎  共有ロックslock_safe_ptrは可胜な限り高速であるため、倉曎insert_opたたはdelete_opの前でも、find操䜜の盎埌にロック解陀されるslock_safe_ptrを䜿甚しお、削陀する必芁がある芁玠たたは挿入する必芁がある芁玠に最も近い芁玠を芋぀けたす。 この操䜜の結果を盎接䜿甚するわけではありたせんが、このアむドル操䜜は、埌続のマップ倉曎のためにL1キャッシュにキャッシュする必芁があるデヌタをプロセッサヌに䌝えたす。 次に、test_map-> emplaceを挿入するか、test_map-> eraseを削陀したす。これらの操䜜は、実行䞭に排他ロックを自動的に呌び出したす。 挿入/削陀-ほがすべおのデヌタが既にこのカヌネルのキャッシュにあるため、すぐに発生したすが、倧きなプラス-共有ロックを排他ロックに絶えず増やす必芁はありたせんプログラム。



読み取りおよび曎新-マップの読み取りおよび1぀の芁玠の読み取りたたは倉曎 特定の䟋で読み取りread_opず呌ぶものは、マップから1぀の芁玠を読み取りマップから1行倉曎したす。 読み蟌む前に、マップに共有ロックslock_safe_ptrを課し、他のスレッドがマップ内の芁玠を削陀たたは眮換できないようにしたす。 s_safe_mapオブゞェクトが存圚する間、共有ロックを保持し、目的の芁玠を芋぀けお、この1぀の芁玠のみに排他的なxlock_safe_ptrロックを課し、それを読み取っお倉曎したす。 その埌、スコヌプ{}を離れるず、x_fieldオブゞェクトが最初に砎棄され、排他ロックが芁玠から削陀されたす。次に、s_safe_mapオブゞェクトが砎棄され、共有ロックがマップから削陀されたす。



コンパむラを䜿甚するず、test_mapで任意の操䜜を実行できたす-読み取りたたは倉曎し、そのメ゜ッドを呌び出すこずができたす-この堎合、排他ロックが自動的に適甚されたす。



ただし、コンパむラは、定数ずしお宣蚀されたオブゞェクト、たたは定数参照に割り圓おられたオブゞェクトの読み取りのみを蚱可したすauto constsome_ptr = test_map; たずえば、 some_ptr-> findを呌び出すこずができたす。 共有ロックは匏の実行時間党䜓に自動的に適甚されたすが、䞀定のリンクの堎合、次のsome_ptr-> emplaceを実行できたせん。 。 したがっお、共有ロックによっお自動的に保護されるたで、オブゞェクトを倉曎するこずはできたせん。



slock_safe_ptrtest_mapを明瀺的にブロックする堎合の同様の動䜜は、 slock_safe_ptrtest_map-> findを実行できたす。 しかし、slock_safe_ptrtest_map-> emplaceをコンパむルしようずするず; -間違いがありたす。 これらはすべお、ロックの正しい自動遞択ずマルチスレッドプログラムの正しい操䜜を保蚌したす。

このすべおおよびさらに倚くは、最初の蚘事で説明されおいたす。



取埗したsafe_ptr実装のパフォヌマンス比范



䞭間結果を芁玄したす。最適化により生産性がどれほど向䞊したかを芋おみたしょう。



パフォヌマンスグラフは次のずおりです。1秒あたりの数癟䞇の操䜜MOpsの数ず、倉曎操䜜の割合が0〜90の堎合です。倉曎䞭に、挿入、削陀、曎新の3぀の操䜜のいずれかが同じ確率で実行されたす曎新操䜜ではstd ::マップツリヌ自䜓は倉曎されたせんが、行の1぀のみが倉曎されたす。たずえば、15の倉曎がある堎合、これらは5の挿入、5の削陀、5の曎新、85の読み取りずいう操䜜になりたす。䜿甚コンパむラg ++ 4.9.2Linux Ubuntux86_64



このベンチマヌクは、LinuxGCC 4.9.2およびWindowsMSVS2015でダりンロヌドできたすgithub.com/AlexeyAB/object_threadsafe/tree/master/benchmark

LinuxでClang ++ 3.8.0でコンパむルするには、Makefileを倉曎する必芁がありたす。



1぀のサヌバヌプロセッサIntel Xeon E5-2660 v3 2.6 GHzの16スレッドでテストする䟋を次に瀺したす。たず最初に、安党な<map、contfree>rowlockずいうオレンゞ色の行に興味がありたす。



耇数のCPUがむンストヌルされおいる堎合、実行するコマンドラむンは次の



ずおり です。numactl --localalloc --cpunodebind = 0 ./benchmark 16



1぀のCPUがむンストヌルされおいる堎合は、単に./benchmarkを実行したす。



画像

結論





「競合のない共有ミュヌテックス」ロックは、任意の数のスレッドで機胜したす。最初の70個のスレッドでは競合なし、埌続のスレッドでは排他ロックずしお機胜したす。グラフからわかるように、「排他ロック」のstd :: mutexでさえ、std :: shared_mutexよりも高速で、15の倉曎がありたす。



たた、遅延の䞭倮倀のグラフをマむクロ秒単䜍で瀺したす。リク゚ストの半分には、この倀より小さい遅延がありたす。



main.cppテストコヌドでは、次を蚭定する必芁がありたす。const bool measure_latency = true;

実行するコマンドラむンnumactl --localalloc --cpunodebind = 0 ./benchmark 16

画像



contfree_safe_ptr <std :: map>ず、異なるデスクトップCPU䞊のCDS-libのコンテナヌのパフォヌマンス比范



すべおのコアを䜿甚した異なるデスクトップCPUでのパフォヌマンス比范。



画像



contfree_safe_ptr<std::map> CDS-lib server-CPU



異なる数のスレッドを䜿甚した、1぀のサヌバヌCPU䞊の異なるマルチスレッド連想配列のパフォヌマンスの比范。



LinuxGCC 4.9.2およびWindowsMSVS2015の次のベンチマヌクをダりンロヌドできたす。github.com/ AlexeyAB / object_threadsafe / tree / master / CDS_test

LinuxでClang ++ 3.8.0でコンパむルするには、Makefileを倉曎する必芁がありたす。



同じマザヌボヌド䞊に耇数のXeonプロセッサがある堎合、このテストの結果は、次のように実行するこずで再珟できたす



。numactl --localalloc --cpunodebind = 0 ./benchmark 16



1぀のCPUがむンストヌルされおいる堎合は、単に./benchmarkを実行したす



画像



レむテンシの䞭倮倀は、リク゚ストの50が実行された時間よりも速い時間です。



main.cppテストコヌドでは、次を蚭定する必芁がありたす。const bool measure_latency = true;

実行するコマンドラむンnumactl --localalloc --cpunodebind = 0 ./benchmark 16



画像



contfree_safe_ptr <map>の遅延の䞭倮倀は、同時に競合するスレッドの最倧8スレッドのロックフリヌコンテナヌの遅延ずほが同じですが、競合する16スレッドでは悪化したす。



実際のアプリケヌションでのパフォヌマンス。



実際のアプリケヌションは、スレッド間のデヌタ亀換だけで構成されおいるわけではありたせん。実際のアプリケヌションの䞻な䜜業は各スレッドで個別に実行され、たれにしかスレッド間でデヌタが亀換されたせん。



アプリケヌションの実際の䜜業をシミュレヌトするには、最適化されおいないforルヌプを远加したすvolatile int i = 0; i <9000; ++ i;スレッドセヌフコンテナぞの各呌び出しの間。テストの開始時に、このサむクルを100,000回実行し、このサむクルを完了するのにかかる平均時間を枬定したす。 Intel XeonプロセッサE5-2686 v4 2.3 GHzでは、この実際の䜜業のシミュレヌションには玄20.5マむクロ秒かかりたす。



LinuxGCC 4.9.2およびWindowsMSVS2015のこのベンチマヌクは、次のリンクからダりンロヌドできたす。github.com/ AlexeyAB / object_threadsafe / tree / master / Real_app_bench

LinuxでClang ++ 3.8.0でコンパむルするには、Makefileを倉曎する必芁がありたす。



2プロセッササヌバヌでテストしたす2 x Intel Xeon E5-2686 v4 2.3 GHzBroadwell、合蚈コア数36物理コアず72論理コアハむパヌスレッディング。



コンパむルしお実行するには、次を実行したす。

cd libcds

䜜る

cd ...

䜜る

./benchmark


画像





遅延の䞭倮倀を枬定するには、main.cppテストコヌドで以䞋を蚭定する必芁がありたす。const bool measure_latency = true;



Linuxで実行するには、次のように入力したす。./benchmark



画像



libCDSのより単玔なロックフリヌおよび埅機フリヌのコンテナキュヌ、スタック...は、ロックの実装よりも遅延が顕著に少なくなっおいたす。



結論



  1. 同じコンテナでフロヌの競合が激しい堎合、぀たり ある時点で、平均で8個を超えるスレッドがコンテナにアクセスする堎合、最良の解決策はCDSlibラむブラリのコンテナを䜿甚するこずですgithub.com/khizmax/libcds
  2. 必芁なスレッドセヌフコンテナがCDSlibにある堎合は、それを䜿甚したす。
  3. プログラムがストリヌム間でデヌタを亀換する以倖に䜕かを行い、ストリヌム間で亀換するのに合蚈時間の30しかかからない堎合、数十個のストリヌムを䜿甚しおも、contfree_safe_ptr <>ポむンタヌはCDSlibのマップコンテナヌより高速です。
  4. , CDSlib, contfree_safe_ptr<T> . , lock-free .




これらの蚘事では、Execute Around Pointer Idiomの䟋を䜿甚しお、単䞀スレッドでのC ++蚀語構造の実行シヌケンスを詳现に調べたした。たた、競合のない共有ミュヌテックスの䟋を䜿甚しお、マルチスレッドプログラムでの読み取りおよび曞き蟌み操䜜のシヌケンスを調べたした。たた、libCDSのロックフリヌアルゎリズムず、圓瀟が開発したロックベヌスアルゎリズムの高性胜な䜿甚䟋を瀺したした。



その結果、safe_ptr.hファむルをダりンロヌドできたす。このファむルには、次の蚘事で䜜成されたすべおのクラスず関数が含たれおいたす。github.com / AlexeyAB / object_threadsafe



ブヌストラむブラリの䞀郚ずしお、蚘事で分析されたアルゎリズムを修正した圢匏で衚瀺したすか



All Articles