sharedへの移行
dmd version 2.030 から、 static およびグローバル変数のデフォルト記憶領域は、 グローバルなデータセグメントではなく thread local storage (TLS) となりました。 ほとんどの既存のコードは問題なくコンパイルが通り正しく実行されるはずですが、 問題が発生することもあります。
TLS変数のパフォーマンス
はい、TLS変数の読み書きは、 昔ながらのグローバル変数よりも遅くなります。 マシン語で1命令であったところが3命令となります。 しかし少なくとも、Linux では PIC (位置非依存コード) を生成する設定でコンパイラを使用した場合の 昔のグローバル変数と比べれば、TLS の方がわずかに高速化です。 したがって、この速度低下が受け入れ難い問題となることはほとんど無いとは思われます。 しかし、仮に問題となったとしましょう。その場合にはどうすればよいでしょう?
- グローバル変数の使用自体を最小にする。 グローバル変数の使用を減らすことは、モジュール化やメンテナンス性の改善に繋がりますから、 これは目指すべき価値のある目標です。
- immutable にする。immutable データには同期の問題がありません。 したがって、 コンパイラはimmutableデータはTLSには配置しません。
- グローバルへの参照をキャッシュする。 ローカルなキャッシュを作り、 元のグローバル変数ではなくキャッシュへの読み書きを行うことで、 特にキャッシュ変数をコンパイラがレジスタに配置できた場合高速化が見込めます。
- __gshared で頑張る
ITLS変数の同定
Tとにかくまずは、グローバル変数を全て探せば、 それがTLS変数です。
ソースコードの複雑性を考えると、 全てのグローバル変数を洗い出すのは簡単ではないかもしれません。 なぜなら、グローバルであることは (かつては) implicit だったので、grep をかける方法がないからです。 全てを漏れなく見つけ出せたと確信するのは難しいでしょう。
新しいdmdコンパイラには、-vtls というスイッチが追加されています。 このスイッチをつけてコンパイルを行うと、 デフォルト処理によってTLSとなっているグローバル変数が全て表示されます。
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への切り替え
immutable データは、一度初期化されたら二度と変更されません。 これは、マルチスレッディングに伴う同期の問題がおこらず、 immutableデータはTLSに置く必要がないことを意味しています。 コンパイラは、immutable データをTLSではなく昔ながらのグローバル記憶域に配置します。
そして、比較的多くのグローバルデータはこのカテゴリに属します。 この場合、immutable とマークをつければ解決です。これは
int[3] table = [6, 123, 0x87];
このようになります:
immutable int[3] table = [6, 123, 0x87];
immutable性は、 コンパイラによるさらなる最適化の機会を与える意味でも優れた性質です。
sharedとマークする
複数のスレッドで共有するグローバルデータは、 shared キーワードで修飾します:
shared int flag;
この修飾によって、flag は昔ながらのグローバル記憶域に配置されると同時に、 sharedであるという型情報が明示的に設定されます:
int* p = &flags; // エラー、flag は shared
shared(int)* q = &flags; // ok
shared 型修飾は推移的です( const や immutable と同様)。 これによって、共有に関する正当性が静的に検査可能になります。
__gsharedで頑張る
時には、 以上の方法が全て使えないこともあります:
- 古い形式のグローバル変数を使っているCのコードとの接続
- "とりあえず動かして後で直す"
- シングルスレッド専用なので共有の問題は無い
- わずかなパフォーマンス改善でも全て必要
- 同期の問題はすべて自前で扱いたい
D はシステムズプログラミングのための言語ですから、もちろん、 これを実現する方法を用意しています。記憶域クラス __gshared です:
__gshared int x;
void main()
{
__gshared int y;
}
__gshared を指定すると、 データは昔ながらのグローバルデータセグメントに配置されます。
当然ながら、__gshared はセーフモード(SafeD)では使用できません。
__gshared を使うことで、 このような危険な部分をコードレビューの際に見つけやすくなりますし、 "後で直す" ための目印にもなります。
コンパイルエラー
TLS関連でもっともよく現れるコンパイルエラーは、以下のものでしょう:
int x;
int* p = &x;
test.d(2): Error: non-constant expression & x
このコードは旧来のグローバル変数では動作しましたが、 TLS変数では動きません。 これは、 TLS変数のアドレスはリンカやローダですら決定できないからです。 このアドレスは実行時に決まる値です。
修正するには、 上記のコードのような初期化は静的コンストラクタで行うようにします。
リンクエラー
しばしば、リンカから、 グローバル変数に関する奇妙なエラーメッセージが発せられることがあります。 ほとんど全ての場合、 これは、あるモジュールではTLSに置かれている変数が、 別のモジュールでは旧来のグローバル領域に置かれていることが原因です。 このような不整合は、古いバージョンの dmd でビルドしたライブラリとリンクしようとしたときに起きます。 libphobos2.a などが正しく最新版に置き換わっているか、注意してください。 他には、Cのコードとの接続の際にもこのエラーが発生します。TLSはCでもサポートされていますが、C のグローバル変数は依然として旧来のグローバル記憶域を使用します。 対応するDの宣言が、 Cのそれと TLS/旧グローバル に関して正しく合っていることの確認が必要です。
int x;
extern int y;
__thread int z;
extern (C)
{
extern shared int x;
shared int y;
extern int z;
}