英語版
このページの英語版を見る

共有コンテンツへの移行

dmd バージョン 2.030 以降では、 静的変数およびグローバル変数のデフォルトの格納クラスは、 スレッドローカルストレージ(TLS)となり、 従来のグローバルデータセグメントではなくなる。 ほとんどの D コードは変更せずにコンパイルして正常に実行できるはずだが、 いくつかの問題が発生する可能性もある。

TLS変数のパフォーマンス

TLS変数の読み取りや書き込みは、従来のグローバル変数よりも遅くなる可能性がある。 正確な違いは、とりわけ、 コンパイラのコード生成設定やターゲットアーキテクチャに依存する。 パフォーマンスの違いは、無視できる程度(Windows)から、 特別な関数(Linux、macOS、*BSDのPIC。オンラインで__tls_get_address を検索)を呼び出すものまである。 非PICの場合、パフォーマンスの違いは はるかに小さく、あるいは無視できる程度である。 もしTLSアクセスがクラシックなグローバル変数よりも遅いことが分かった場合、 その問題をどう解決すればよいだろうか?

  1. グローバル変数の使用を最小限に抑える。グローバル変数の使用を減らすことで、 コードのモジュール化と保守性が向上する ため、これは価値のある目標である。
  2. 関連するグローバル変数を構造体にまとめる。TLS関連の オーバーヘッドは(多くの場合)アクセスされる変数の数に比例する。 グローバル構造体変数は単一の変数であるため、 構造体のメンバー1つにアクセスした後、別のメンバーにアクセスすると 「無料」(TLS関連の余分なオーバーヘッドは発生しない)となる。 もちろん、データを構造体にグループ化した場合、おそらく 構造体への参照を渡してコンテキストとして使用することで、グローバル変数を完全に削除すべきである...
  3. グローバル変数を不変にする。不変のデータには "synchronized"の問題がないため、コンパイラはそれを TLSに配置しない。
  4. グローバル変数の参照をキャッシュする。 ローカルキャッシュを作成し、 オリジナルの値ではなくキャッシュされた値にアクセスすることで、 特にキャッシュされた値がコンパイラによって登録された場合は、処理速度が向上する可能性がある。
  5. __gshared で試してみよう。

TLS変数の特定

最初のステップは、すべてのグローバル変数を見つけ、 それらを処分するかどうか検討することである。

ソースコードが複雑であるため、 グローバル変数を特定するのは必ずしも容易ではない。 暗黙的であったため、それらを検索する方法がない。 すべてを見つけられたかどうかを確かめるのは難しい。

新しいdmdコンパイラスイッチが追加された。-vtls 。 このスイッチをオンにしてコンパイルすると、 スレッドローカルストレージにデフォルト設定されているすべてのグローバル変数のリストが 表示される。

int x;

void main()
{
    static int y;
}
dmd test -vtls
test.d(2): x is thread local
test.d(6): y is thread local

Immutableに切り替える

不変のデータは、一度初期化されると変更されることはない。 これは、マルチスレッド処理における同期の問題がないことを意味し、 不変のデータを TLS に置く必要がないことを意味する。 コンパイラは、不変のデータを TLS ではなく、従来のグローバルストレージに置く。

グローバルデータの大部分がこのカテゴリーに該当する。immutable とマークするだけで完了する。

int[3] table = [6, 123, 0x87];

次のように変更される。

immutable int[3] table = [6, 123, 0x87];

不変性は、さらなるコンパイラの最適化への扉を開くという点でも素晴らしい。

共有としてマークする

複数のスレッドで共有されることを意図したグローバルデータは、shared キーワードでマークする必要がある。

shared int flag;

これにより、flag が 従来のグローバルストレージに格納されるだけでなく、共有型として型付けされます。

int* p = &flag;           // エラー、フラグが共有されている
shared(int)* q = &flag;   // OK

shared の型属性は推移的(constimmutable のような)である。これにより、 静的チェックと共有の正確性を実現できる。

"__gshared"の使用

時には、上記のいずれの解決策も受け入れられない場合がある。

  1. 古典的なグローバル変数を使用するCコードとのインターフェイスが
  2. とりあえず動くようにして、後で修正する
  3. アプリケーションがシングルスレッドのみで、共有の問題がない
  4. パフォーマンスを最大限に引き出す必要がある
  5. すべての同期問題を自分で処理したい 場合

D言語はシステム言語なので、もちろん、 これを行う方法はある。ストレージクラス__gshared を使用すると、

__gshared int x;

void main()
{
    __gshared int y;
}

__gshared 変数を古典的なグローバルデータセグメントに格納する 。

当然ながら、__gshared はセーフモードでは許可されない。

__gshared を使用すると、そのようなコードは 品質保証(QA)のコードレビューを行う際や、後で戻って回避策を修正する際に 簡単に検索できるようになる。

コンパイルエラー

TLSに関連する最も一般的なコンパイラエラーは次のようになる。

int x;
int* p = &x;
test.d(2): Error: non-constant expression & x

これは従来のグローバル変数では機能するが、 TLS変数では機能しない。その理由は、 TLS変数には、リンカーやローダーが認識するメモリ上の位置がないからである。 これは実行時に計算される値である。

解決策は、そのようなものを静的コンストラクタで初期化することである。

リンクエラー

リンカからグローバル変数に関する奇妙なエラーメッセージが表示されることがある。 これは、ほぼ常に、あるモジュールが変数をTLSに配置し、別のモジュールが同じ変数をクラシックグローバルストレージに配置していることが原因である。 これは、以前のバージョンのdmdでビルドされたライブラリとコードをリンクする際に発生する可能性がある。 libphobos2.aが適切に最新のものに置き換えられていることを確認する。 また、C言語とのインターフェイスでも発生する可能性がある。C言語のグローバル変数は、 デフォルトでクラシックグローバルになるが、C言語はTLS宣言をサポートしている。 TLSまたはクラシックグローバルの観点で、対応するD言語の宣言がC言語の宣言と一致していることを確認する。

int x;
extern int y;
__thread int z;
extern (C)
{
    extern shared int x;
    shared int y;
    extern int z;
}