Exploring the .NET CoreFX Part 2: Cache ThreadLocal Variables in Locals

Exploring the .NET CoreFX Part 2: Cache ThreadLocal Variables in Locals

Page content

This is part 2 of my Exploring the .NET CoreFX Series.

Thread-local storage allows you to mark a global or static variable as local to a thread. In Win32, thread-local storage is provided by the functions TlsAlloc, TlsGetValue, TlsSetValue, and TlsFree. Similarly, C# provides System.ThreadStaticAttribute and System.Threading.ThreadLocal.

Unfortunately, thread-local storage comes at a cost. Reading or writing a thread-local variable is far more expensive than reading or writing a local variable. System.Collections.Immutable uses a trick or two to help ameliorate this expense. For example, System.Collections.Immutable caches thread-local variables in local variables in a method to avoid unnecessary TLS hits on repeated access. Here’s some sample code which implements this:

[ThreadStatic]
private static Stack<RefAsValueType<T>> stack;

public static void TryAdd(T item)
{
    Stack<RefAsValueType<T>> localStack = stack; // cache in a local to avoid unnecessary TLS hits on repeated accesses
    if (localStack == null)
    {
        stack = localStack = new Stack<RefAsValueType<T>>(MaxSize);
    }

    // Just in case we're in a scenario where an object is continually requested on one thread
    // and returned on another, avoid unbounded growth of the stack.
    if (localStack.Count < MaxSize)
    {
        localStack.Push(new RefAsValueType<T>(item));
    }
}

Recommendations

  1. Minimize the use of thread-local storage. If you can, avoid it entirely.
  2. Minimize the number of times code accesses TLS variables. Consider caching thread-local variables in local variables in a method.