Avoidance of Heap Contention
The new home for Visual Studio documentation is Visual Studio 2017 Documentation on docs.microsoft.com.
The latest version of this topic can be found at Avoidance of Heap Contention.
The default string managers provided by MFC and ATL are simple wrappers on top of a global heap. This global heap is fully thread-safe, meaning that multiple threads can allocate and free memory from it simultaneously without corrupting the heap. To help provide thread safety, the heap has to serialize access to itself. This is usually accomplished with a critical section or similar locking mechanism. Whenever two threads try to access the heap simultaneously, one thread is blocked until the other thread's request is finished. For many applications, this situation rarely occurs and the performance impact of the heap's locking mechanism is negligible. However, for applications that frequently access the heap from multiple threads contention for the heap's lock can cause the application to run slower than if it were single-threaded (even on machines with multiple CPUs).
Applications that use CStringT are especially susceptible to heap contention because operations on CStringT
objects frequently require reallocation of the string buffer.
One way to alleviate heap contention between threads is to have each thread allocate strings from a private, thread-local heap. As long as the strings allocated with a particular thread's allocator are used only in that thread, the allocator need not be thread-safe.
Example
The example below illustrates a thread procedure that allocates its own private non-thread-safe heap to use for strings on that thread:
DWORD WINAPI WorkerThreadProc(void* pBase)
{
// Declare a non-thread-safe heap just for this thread:
CWin32Heap stringHeap(HEAP_NO_SERIALIZE, 0, 0);
// Declare a string manager that uses the thread's heap:
CAtlStringMgr stringMgr(&stringHeap);
int nBase = *((int*)pBase);
int n = 1;
for(int nPower = 0; nPower < 10; nPower++)
{
// Use the thread's string manager, instead of the default:
CString strPower(&stringMgr);
strPower.Format(_T("%d"), n);
_tprintf_s(_T("%s\n"), strPower);
n *= nBase;
}
return(0);
}
Comments
Multiple threads could be running using this same thread procedure but since each thread has its own heap there is no contention between threads. In addition, the fact that each heap is not thread-safe gives a measurable increase in performance even if just one copy of the thread is running. This is the result of the heap not using expensive interlocked operations to protect against concurrent access.
For a more complicated thread procedure, it may be convenient to store a pointer to the thread's string manager in a thread local storage (TLS) slot. This allows other functions called by the thread procedure to access the thread's string manager.