6

thread_local非常に頻繁にアクセスされるスレッドごとのブール値フラグにC++11 キーワードを使用したいと考えていました。

ただし、ほとんどのコンパイラは、整数 ID (スロット) を現在のスレッドの変数のアドレスにマップするテーブルを使用して、スレッド ローカル ストレージを実装しているようです。このルックアップは、パフォーマンスが重要なコード パス内で発生するため、そのパフォーマンスについて懸念があります。

スレッド ローカル ストレージが実装されることを期待していた方法は、スレッドに応じて異なる物理ページによってサポートされる仮想メモリ範囲を割り当てることです。そうすれば、フラグへのアクセスは、MMU がマッピングを処理するため、他のメモリ アクセスと同じコストになります。

主流のコンパイラのどれも、このようにページ テーブル マッピングを利用しないのはなぜですか?

mmapLinux とVirtualAllocWin32 で独自の「スレッド固有のページ」を実装できると思いますが、これはかなり一般的な使用例のようです。誰かが既存の解決策またはより良い解決策を知っている場合は、それらを教えてください。

std::atomic<std::thread::id>また、アクティブなスレッドを表すために各オブジェクト内に を格納することも検討しましたが、プロファイルを作成すると、チェックstd::this_thread::get_id() == active_threadが非常に高価であることがわかります。

4

6 に答える 6

6

Linux/x86-64 では、スレッド ローカル ストレージは特別なセグメント レジスタを介して実装されます%fs( x86-64 ABIページ 23...)

したがって、次のコード (C + GCC 拡張__thread構文を使用していますが、C++11 と同じですthread_local)

__thread int x;
int f(void) { return x; }

にコンパイルされます(でgcc -O -fverbose-asm -S):

         .text
 .Ltext0:
         .globl  f
         .type   f, @function
 f:
 .LFB0:
         .file 1 "tl.c"
         .loc 1 3 0
         .cfi_startproc
         .loc 1 3 0
         movl    %fs:x@tpoff, %eax       # x,
         ret
         .cfi_endproc
 .LFE0:
         .size   f, .-f
         .globl  x
         .section        .tbss,"awT",@nobits
         .align 4
         .type   x, @object
         .size   x, 4
 x:
         .zero   4

したがって、あなたの懸念に反して、Linux/x86-64 では TLS へのアクセスは非常に高速です。テーブルとして正確に実装されているわけではありません (代わりに、カーネルとランタイムが%fsセグメント レジスタを管理してスレッド固有のメモリ ゾーンを指し、コンパイラとリンカがそこでオフセットを管理します)。ただし、古いpthread_getspecificは確かにテーブルを通過しましたが、TLS を使用するとほとんど役に立ちません。

ところで、定義により、プロセスには独自の単一のアドレス空間があるため、同じプロセス内のすべてのスレッドは仮想メモリ内の同じアドレス空間を共有します。(その他を参照してください...詳細についてはproc(5)を 参照してください。また、 mmap(2)も参照してください。C++11 スレッド ライブラリは、 clone(2)を使用して実装されるpthreadsに基づいています)。したがって、「スレッド固有のメモリ マッピング」は矛盾しています。タスク (カーネル スケジューラによって実行されるもの) が独自のアドレス空間を持つと、それはプロセス (スレッドではなく) と呼ばれます。スレッドの特徴/proc/self/maps/proc/同じプロセスで、共通のアドレス空間 (およびファイル記述子などの他のエンティティ) を共有することです。

于 2014-10-18T09:05:02.250 に答える
3

Linux、OSX、Windows などの主流のオペレーティング システムは、ページ マッピングをスレッドごとではなく、プロセスごとのプロパティにします。これには非常に正当な理由があります。ページ マッピング テーブルは RAM に格納されており、それを読み取って有効な物理アドレスを計算すると、すべての命令に対してこれを行う必要がある場合、非常にコストがかかります。

そのため、プロセッサはそうではなく、最近使用されたマッピング テーブル エントリのコピーを、実行コアに近い高速メモリに保持します。TLB キャッシュと呼ばれます。

TLB キャッシュの無効化は非常にコストがかかります。メモリ キャッシュの 1 つでデータが使用可能になる可能性は低く、RAM から再ロードする必要があります。これが発生する必要がある場合、プロセッサは何千サイクルも停止する可能性があります。

したがって、提案されたスキームは実際には非常に非効率的である可能性が高く、オペレーティングシステムがそれをサポートすると仮定すると、インデックス付きルックアップを使用する方が安価です。プロセッサは単純な計算に非常に優れており、ギガヘルツで発生し、メモリへのアクセスはメガヘルツで発生します。

于 2014-10-18T10:19:25.093 に答える
3

thread_local他のスレッドがポインターを介して変数にアクセスできなくなるため、この提案は機能しません。それらのスレッドは、その変数の独自のコピーにアクセスすることになります。

たとえば、メイン スレッドと 100 個のワーカー スレッドがあるとします。worker_threads は、自身のthread_local変数へのポインタをメイン スレッドに戻します。メインスレッドには、これらの 100 個の変数への 100 個のポインターが含まれるようになりました。TLS メモリが提案どおりにページ テーブルにマップされている場合、メイン スレッドは、メイン スレッドの TLS 内の初期化されていない単一の変数への 100 個の同一のポインタを持つことになります。

于 2015-10-28T11:06:11.137 に答える
2

メモリ マッピングはスレッドごとではなく、プロセスごとです。すべてのスレッドが同じマッピングを共有します。

カーネルはスレッドごとのマッピングを提供できますが、現在は提供していません。

于 2014-10-18T09:15:00.993 に答える