forked from dlang/dlang.org
-
Notifications
You must be signed in to change notification settings - Fork 4
/
migrate-to-shared.dd
232 lines (187 loc) · 7.83 KB
/
migrate-to-shared.dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
Ddoc
$(D_S $(TITLE),
$(P $(LINK2 changelog.html#new2_030, dmd version 2.030) から、
static
およびグローバル変数のデフォルト記憶領域は、
グローバルなデータセグメントではなく $(LINK2 glossary.html#tls, thread local storage (TLS))
となりました。
ほとんどの既存のコードは問題なくコンパイルが通り正しく実行されるはずですが、
問題が発生することもあります。
)
$(OL
$(LI $(LINK2 #performance, TLS変数のパフォーマンス).)
$(LI $(LINK2 #vtls, TLS変数の同定).)
$(LI $(LINK2 #immutable, immutableへの切り替え).)
$(LI $(LINK2 #shared, sharedとマークする).)
$(LI $(LINK2 #__gshared, __gsharedで頑張る).)
$(LI $(LINK2 #compile-errors, コンパイルエラー).)
$(LI $(LINK2 #link-errors, リンクエラー).)
)
$(H3 $(LNAME2 performance, TLS変数のパフォーマンス))
$(P はい、TLS変数の読み書きは、
昔ながらのグローバル変数よりも遅くなります。
マシン語で1命令であったところが3命令となります。
しかし少なくとも、Linux では PIC (位置非依存コード)
を生成する設定でコンパイラを使用した場合の
昔のグローバル変数と比べれば、TLS の方がわずかに高速化です。
したがって、この速度低下が受け入れ難い問題となることはほとんど無いとは思われます。
しかし、仮に問題となったとしましょう。その場合にはどうすればよいでしょう?
)
$(OL
$(LI グローバル変数の使用自体を最小にする。
グローバル変数の使用を減らすことは、モジュール化やメンテナンス性の改善に繋がりますから、
これは目指すべき価値のある目標です。)
$(LI immutable にする。immutable データには同期の問題がありません。
したがって、
コンパイラはimmutableデータはTLSには配置しません。)
$(LI グローバルへの参照をキャッシュする。
ローカルなキャッシュを作り、
元のグローバル変数ではなくキャッシュへの読み書きを行うことで、
特にキャッシュ変数をコンパイラがレジスタに配置できた場合高速化が見込めます。)
$(LI $(B __gshared) で頑張る)
)
$(H3 $(LNAME2 vtls, ITLS変数の同定))
$(P Tとにかくまずは、グローバル変数を全て探せば、
それがTLS変数です。
)
$(P ソースコードの複雑性を考えると、
全てのグローバル変数を洗い出すのは簡単ではないかもしれません。
なぜなら、グローバルであることは (かつては) implicit だったので、grep をかける方法がないからです。
全てを漏れなく見つけ出せたと確信するのは難しいでしょう。
)
$(P 新しいdmdコンパイラには、$(B -vtls) というスイッチが追加されています。
このスイッチをつけてコンパイルを行うと、
デフォルト処理によってTLSとなっているグローバル変数が全て表示されます。
)
---
int x;
void main()
{
static int y;
}
---
$(CONSOLE
dmd test $(B -vtls)
test.d(2): x is thread local
test.d(6): y is thread local
)
$(H3 $(LNAME2 immutable, immutableへの切り替え))
$(P immutable データは、一度初期化されたら二度と変更されません。
これは、マルチスレッディングに伴う同期の問題がおこらず、
immutableデータはTLSに置く必要がないことを意味しています。
コンパイラは、immutable
データをTLSではなく昔ながらのグローバル記憶域に配置します。
)
$(P そして、比較的多くのグローバルデータはこのカテゴリに属します。
この場合、$(B immutable) とマークをつければ解決です。これは
)
---
int[3] table = [6, 123, 0x87];
---
$(P このようになります:)
---
immutable int[3] table = [6, 123, 0x87];
---
$(P immutable性は、
コンパイラによるさらなる最適化の機会を与える意味でも優れた性質です。
)
$(H3 $(LNAME2 shared, sharedとマークする))
$(P 複数のスレッドで共有するグローバルデータは、
$(B shared) キーワードで修飾します:
)
---
shared int flag;
---
$(P この修飾によって、$(CODE flag) は昔ながらのグローバル記憶域に配置されると同時に、
sharedであるという型情報が明示的に設定されます:
)
---
int* p = &flags; // エラー、flag は shared
shared(int)* q = &flags; // ok
---
$(P $(CODE shared) 型修飾は推移的です(
$(CODE const) や $(CODE immutable) と同様)。
これによって、共有に関する正当性が静的に検査可能になります。
)
$(H3 $(LNAME2 __gshared, __gsharedで頑張る))
$(P 時には、
以上の方法が全て使えないこともあります:
)
$(OL
$(LI 古い形式のグローバル変数を使っているCのコードとの接続)
$(LI "とりあえず動かして後で直す")
$(LI シングルスレッド専用なので共有の問題は無い)
$(LI わずかなパフォーマンス改善でも全て必要)
$(LI 同期の問題はすべて自前で扱いたい
)
)
$(P D はシステムズプログラミングのための言語ですから、もちろん、
これを実現する方法を用意しています。記憶域クラス $(B __gshared) です:
)
---
__gshared int x;
void main()
{
__gshared int y;
}
---
$(P $(B __gshared) を指定すると、
データは昔ながらのグローバルデータセグメントに配置されます。
)
$(P 当然ながら、$(B __gshared) はセーフモード(SafeD)では使用できません。
)
$(P $(B __gshared) を使うことで、
このような危険な部分をコードレビューの際に見つけやすくなりますし、
"後で直す" ための目印にもなります。
)
$(H3 $(LNAME2 compile-errors, コンパイルエラー))
$(P TLS関連でもっともよく現れるコンパイルエラーは、以下のものでしょう:
)
---
int x;
int* p = &x;
---
$(CONSOLE
test.d(2): Error: non-constant expression & x
)
$(P このコードは旧来のグローバル変数では動作しましたが、
TLS変数では動きません。
これは、
TLS変数のアドレスはリンカやローダですら決定できないからです。
このアドレスは実行時に決まる値です。)
$(P 修正するには、
上記のコードのような初期化は静的コンストラクタで行うようにします。)
$(H3 $(LNAME2 link-errors, リンクエラー))
$(P しばしば、リンカから、
グローバル変数に関する奇妙なエラーメッセージが発せられることがあります。
ほとんど全ての場合、
これは、あるモジュールではTLSに置かれている変数が、
別のモジュールでは旧来のグローバル領域に置かれていることが原因です。
このような不整合は、古いバージョンの dmd
でビルドしたライブラリとリンクしようとしたときに起きます。
libphobos2.a などが正しく最新版に置き換わっているか、注意してください。
他には、Cのコードとの接続の際にもこのエラーが発生します。TLSはCでもサポートされていますが、C
のグローバル変数は依然として旧来のグローバル記憶域を使用します。
対応するDの宣言が、
Cのそれと TLS/旧グローバル に関して正しく合っていることの確認が必要です。
)
$(CCODE
int x;
extern int y;
__thread int z;
)
---
extern (C)
{
extern shared int x;
shared int y;
extern int z;
}
---
)
Macros:
H2=<h2>$0</h2>
H3=<h3>$0</h3>
TITLE=sharedへの移行
WIKI=Migrating To Shared
CATEGORY_ARTICLES=$0