Migrating to Shared
Starting with dmd version 2.030, the default storage class for statics and globals will be thread local storage (TLS), rather than the classic global data segment. While most D code should just compile and run successfully without change, there can be some issues.
Performance of TLS variables
Yes, reading or writing TLS variables will be slower than for classic global variables. It'll be about 3 instructions rather than one. But on Linux, at least, TLS will be slightly faster than accessing classic globals using PIC (position independent code) compiler code generation settings. So it's hard to see that this would be an unacceptable problem. But let's presume it is. What can we do about it?
- Minimize use of global variables. Reducing the use of globals can improve the modularization and maintainability of the code anyway, so this is a worthy goal.
- Make them immutable. Immutable data doesn't have synchronization problems, so the compiler doesn't place it in TLS.
- Cache a reference to the global. Making a local cache of it, and then accessing the cached value rather than the original, can speed things up especially if the cached value gets enregistered by the compiler.
- Cowboy it with __gshared.
Identifying TLS variables
The first step is to find all the global variables, so they can be reviewed for disposition.
Given the complexity of source code, it isn't always easy to identify the global variables. Since it (used to be) implicit, there's no way to grep for them. It's hard to be sure you've found them all.
A new dmd compiler switch was added, -vtls. Compiling with it on will print a list of all the global variables that are now defaulting to thread local storage.
int x; void main() { static int y; }
dmd test -vtls test.d(2): x is thread local test.d(6): y is thread local
Switch to Immutable
Immutable data, once initialized, never changes. This means that there are no synchronization issues with multithreading, and so no need to put immutable data into TLS. The compiler will put immutable data into classic global storage, not TLS.
A good chunk of global data falls into this category. Just mark it as immutable and it's done.
int[3] table = [6, 123, 0x87];
becomes:
immutable int[3] table = [6, 123, 0x87];
Immutability is also nice because it opens the door for further compiler optimizations.
Marking As Shared
Global data that is meant to be shared among multiple threads should be marked with the shared keyword:
shared int flag;
Not only does this cause flag to be put into classic global storage, it is also typed as being shared:
int* p = &flag; // error, flag is shared shared(int)* q = &flag; // ok
The shared type attribute is transitive (like const and immutable) are. This enables static checking and sharing correctness.
Cowboying With __gshared
Sometimes, none of the above solutions will be acceptable:
- interfacing with C code that uses classic globals
- just get it working, and go back and fix it later
- the app is single threaded only, so no sharing issues
- you need every erg of performance
- you want to handle all the synchronization issues yourself
As D is a systems language, of course there's a way to do this. Use the storage class __gshared:
__gshared int x; void main() { __gshared int y; }
__gshared stores the variable in the classic global data segment.
Naturally, __gshared is not allowed in safe mode.
Using __gshared makes such code easily searchable when doing QA code reviews or when going back later to fix any workarounds.
Compile Errors
The most common compiler error related to TLS will be:
int x; int* p = &x;
test.d(2): Error: non-constant expression & x
While this works with classic global variables, it won't work with TLS variables. The reason is because TLS variables don't have a location in memory known to either the linker or the loader. It's a runtime computed value.
The solution is to initialize such things in a static constructor.
Link Errors
Sometimes you may encounter strange error messages from the linker about the global variables. These are nearly always caused by one module putting the variable in TLS, and another putting that same variable in classic global storage. This can happen when linking code with libraries that were built with earlier versions of dmd. Check that libphobos2.a was properly replaced with the latest. It can also happen when interfacing with C. C globals default to being classic global, although C does support TLS declarations. Make sure the corresponding D declarations match the C ones in terms of TLS or classic global.
int x; extern int y; __thread int z;
extern (C) { extern shared int x; shared int y; extern int z; }