will be predominantly multi- thread/multi-core • Xbox 360 has 3 cores • PS3 will be multi-core • >70% of PC sales will be multi-core by end of 2006 • Most Windows Vista systems will be multi-core • Two performance possibilities: • Single-threaded? Minimal performance growth • Multi-threaded? Exponential performance growth
Xbox 360 • Easy to multithread • Allows use of aggressive compression to improve load times • Don’t throw a thread at a problem better solved by offline processing • Texture compression, file packing, etc.
multiple threads (D3DCREATE_MULTITHREADED) works poorly • Exception: Xbox 360 command buffers • Special case of cascades paradigm • Pass render state from update to render • With constant workload gives same latency, better frame rate • With increased workload gives same frame rate, worse latency
Procedurally generated animating cloud textures • Cloth simulations • Dynamic ambient occlusion • Procedurally generated vegetation, etc. • Extra particles, better particle physics, etc. • Easy to synchronize • Potentially expensive, but if the core is otherwise idle...?
• Makes use of three threads • May be too much latency • Could run physics on many threads • Uses many threads while doing physics • May leave threads mostly idle elsewhere
software thread per core • 3-6 on Xbox 360 • 1-? on PC (1-4 for now, need to query) • Too many busy threads adds complexity, and lowers performance • Context switches are not free • Can have many non-CPU intensive threads • I/O threads that block, or intermittent tasks
threads • Not the same as double the number of cores • Can give a small perf boost • Can cause a perf drop • Can avoid scheduler latency • Ideally one heavy thread per core plus some additional intermittent threads
Rendering was taking half of time—put on separate thread • Two render-description buffers created to communicate from update to render • Linear read/write access for best cache usage • Doesn't copy const data • File I/O and decompress on other threads
• Don't use TerminateThread() • Bad idea on Windows: leaves the process in an indeterminate state, doesn't allow clean-up, etc. • Unavailable on Xbox 360 • Instead return from your thread function, or call ExitThread
= CreateThread(0, stackSize, ThreadFunctionBad, 0, 0, 0); // Do work on main thread here. for (;;) { // Wait for child thread to complete DWORD exitCode; GetExitCodeThread(hThread, &exitCode); if (exitCode != STILL_ACTIVE) break; } ... DWORD __stdcall ThreadFunctionBad(void* data) { #ifdef WIN32 SetThreadAffinityMask(GetCurrentThread(), 8); #endif // Do child thread work here. return 0; } CreateThread doesn't initialize C runtime Stack size of zero means inherit parent's stack size Busy waiting is bad! Don't forget to close this when done with it Be careful with thread affinities on Windows
= (HANDLE)_beginthreadex(0, stackSize, ThreadFunction, 0, 0, 0); // Do work on main thread here. // Wait for child thread to complete WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); ... unsigned __stdcall ThreadFunction(void* data) { #ifdef XBOX // On Xbox 360 you must explicitly assign // software threads to hardware threads. XSetThreadProcessor(GetCurrentThread(), 2); #endif // Do child thread work here. return 0; } _beginthreadex initializes CRT Specify stack size on Xbox 360 The correct way to wait for a thread to exit Don't forget to close this when done with it Thread affinities must be specified on Xbox 360
to parallelize loops and some other constructs • Works best on long symmetric tasks— particles? • Game tasks are short—16.6 ms • Many game tasks are not symmetric • OpenMP is nice, but not ideal
Critical Sections • Don't use SuspendThread() • Some title have used this for synchronization • Can easily lead to deadlocks • Interacts badly with Visual Studio debugger
share resources without locking • Includes InterlockedXXX(), lockless message passing, Double Checked Locking, etc. • Very hard to get right: • Compiler can reorder instructions • CPU can reorder instructions • CPU can reorder reads and writes • Not as fast as avoiding synchronization entirely
the message to be 'empty'. while (g_msg.filled) ; memcpy(g_msg.data, input, MESSAGESIZE); g_msg.filled = true; } void GetMessage() { // Wait for the message to be 'filled'. while (!g_msg.filled) ; memcpy(localMsg.data, g_msg.data, MESSAGESIZE); g_msg.filled = false; }
no contention • Hundreds to thousands of cycles • Synchronization can be arbitrarily expensive when there is contention! • Goals: • Synchronize rarely • Hold locks briefly • Minimize shared data
• Consider per-thread heaps with no locking • HEAP_NO_SERIALIZE flag avoids lock on Win32 heaps • Consider custom single-purpose allocators • Consider avoiding memory allocations! • Avoid synch in in-house profilers • D3DCREATE_MULTITHREADED causes synchronization on almost every Direct3D call
and asynchronous I/O • Then: consider compression to accelerate loading • Don't do format conversions etc. that are better done at build time! • Have resource proxies to allow rendering to continue
decompressor locks g_resources while decompressing • Better design: decompressor adds resources to vector after decompressing • Still requires renderer to synch on every resource access • Best design: two Resource* vectors • Renderer has private vector, no locking required • Decompressor use shared vector, syncs when adding new Resource* • Renderer moves Resource* from shared to private vector once per frame
hide many synchronization stalls • Home-grown spin locks make profiling harder • Consider instrumenting calls to synchronization functions • Don't use locks in instrumentation—use TLS variables to store results • Windows: Intel VTune, AMD CodeAnalyst, and the Visual Studio Team System Profiler • Xbox 360: PIX, XbPerfView, etc.
does support multi-threaded debugging • Use threads window • Use @hwthread in watch window on Xbox 360 • KD and WinDBG support multi-threaded debugging • Thread Local Storage (TLS) • __declspec(thread) declares per-thread variables • But doesn't work in dynamically loaded DLLs • TLSAlloc is less efficient, less convenient, but works in dynamically loaded DLLs
works, it’s really really slow • Best to do all calls to Direct3D from a single thread • Could pass off locked resource pointers to a queue for a loading threads to work with • Test on multiple machines and configurations • Single-core, SMT (i.e. Hyper-Threading), Dual- core, Intel and AMD chips, Multi-socket multicore (4+ cores)
series of WaitForSingleObject calls • The OS is highly optimized around multithreading and event-based blocking • I/O Completion Ports • Very efficient way to have the OS assign a pool of worker threads to incoming I/O requests • Useful construct for implementing a game server
in GetSystemInfo(), so a 2 could mean a SMT machine with only 1 actual core –or- 2 cores • Detailed Win32 APIs exposing this distinction not available until Windows XP x64, Windows Server 2003 SP1, Windows Vista, etc. • GetLogicalProcessorInformation() • For now you have to use CPUID detailed by Intel and AMD to parse this out…
between cores! • As your thread moves from core to core, results of RDTSC counter deltas may be nonsense • CPU frequency itself can change at run-time through speed step technologies • See Power Management APIs for more information • Best thing to do is use Win32 API QueryPerformanceCounter / QueryPerformanceFrequency • See DirectX SDK article Game Timing and Multiple Cores
useful for assigning ‘heavy’ work threads • This mask is technically a hint, not a commitment • RDTSC-based instrumenting will require locking the game threads to a single core • Otherwise let the Windows scheduler do the right thing • CreateDevice/Reset might have a side-effect on the calling thread’s affinity with software vertex processing enabled
of thread creation/destruction • For some DLLs this is required to initialize TLS • For many this is a waste of time, so call DisableThreadLibraryCalls() from your DllMain during process creation (DLL_PROCESS_ATTACH) • The OS serializes access to the entry point • This means threads created during DllMain won’ t start for a while, so don’t wait on them in the DLL startup
360, the Xbox logo, and XNA are either registered trademarks or trademarks of Microsoft Corporation in the United Sates and / or other countries. This presentation is for informational purposes only. Microsoft makes no warranties, express or implied, in this summary.