//========== Copyright © 2007, Valve Corporation, All rights reserved. ======== // // Purpose: // //============================================================================= #include "pch_tier0.h" #include "tier0/tslist.h" #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #endif #include #include "tier0/threadtools.h" // for rand() // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" extern ThreadHandle_t * CreateTestThreads( ThreadFunc_t fnThread, int numThreads, int nProcessorsToDistribute ); extern void JoinTestThreads( ThreadHandle_t *pHandles ); namespace TSListTests { int NUM_TEST = 10000; int NUM_THREADS; int MAX_THREADS = 8; int NUM_PROCESSORS = 1; CInterlockedInt g_nTested; CInterlockedInt g_nThreads; CInterlockedInt g_nPushThreads; CInterlockedInt g_nPopThreads; CInterlockedInt g_nPushes; CInterlockedInt g_nPops; CTSQueue g_TestQueue; CTSList g_TestList; volatile bool g_bStart; int *g_pTestBuckets; CTSListBase g_Test; TSLNodeBase_t **g_nodes; int idx = 0; const char *g_pListType; class CTestOps { public: virtual void Push( int item ) = 0; virtual bool Pop( int *pResult ) = 0; virtual bool Validate() { return true; } virtual bool IsEmpty() = 0; }; bool g_bUseMutex = false; CThreadConditionalMutex< CThreadFastMutex, &g_bUseMutex > g_TestLock; class CQueueOps : public CTestOps { void Push( int item ) { g_TestLock.Lock(); g_TestQueue.PushItem( item ); g_TestLock.Unlock(); g_nPushes++; } bool Pop( int *pResult ) { g_TestLock.Lock(); if ( g_TestQueue.PopItem( pResult ) ) { g_TestLock.Unlock(); g_nPops++; return true; } g_TestLock.Unlock(); return false; } bool Validate() { return true; //g_TestQueue.Validate(); } bool IsEmpty() { return ( g_TestQueue.Count() == 0 ); } } g_QueueOps; class CListOps : public CTestOps { void Push( int item ) { g_TestLock.Lock(); g_TestList.PushItem( item ); g_nPushes++; } bool Pop( int *pResult ) { g_TestLock.Lock(); if ( g_TestList.PopItem( pResult ) ) { g_TestLock.Unlock(); g_nPops++; return true; } g_TestLock.Unlock(); return false; } bool Validate() { return true; } bool IsEmpty() { return ( g_TestList.Count() == 0 ); } } g_ListOps; CTestOps *g_pTestOps; void ClearBuckets() { memset( g_pTestBuckets, 0, sizeof(int) * NUM_TEST ); } void IncBucket( int i ) { if ( i < NUM_TEST ) // tests can slop over a bit { ThreadInterlockedIncrement( &g_pTestBuckets[i] ); } } void DecBucket( int i ) { if ( i < NUM_TEST ) // tests can slop over a bit { ThreadInterlockedDecrement( &g_pTestBuckets[i] ); } } void ValidateBuckets() { for ( int i = 0; i < NUM_TEST; i++ ) { if ( g_pTestBuckets[i] != 0 ) { Msg( "Test bucket %d has an invalid value %d\n", i, g_pTestBuckets[i] ); DebuggerBreakIfDebugging(); return; } } } uintp PopThreadFunc( void *) { //ThreadSetDebugName( "PopThread" ); g_nPopThreads++; g_nThreads++; while ( !g_bStart ) { ThreadSleep( 1 ); } int ignored; for (;;) { if ( !g_pTestOps->Pop( &ignored ) ) { ThreadPause(); ThreadSleep(0); if ( g_nPushThreads == 0 ) { // Pop the rest while ( g_pTestOps->Pop( &ignored ) ) { ThreadPause(); ThreadSleep( 0 ); } break; } } } g_nThreads--; g_nPopThreads--; return 0; } uintp PushThreadFunc( void * ) { //ThreadSetDebugName( "PushThread" ); g_nPushThreads++; g_nThreads++; while ( !g_bStart ) { ThreadPause(); ThreadSleep( 0 ); } while ( ++g_nTested <= NUM_TEST ) { g_pTestOps->Push( g_nTested ); } g_nThreads--; g_nPushThreads--; return 0; } void TestStart() { g_nTested = 0; g_nThreads = 0; g_nPushThreads = 0; g_nPopThreads = 0; g_bStart = false; g_nPops = g_nPushes = 0; ClearBuckets(); } void TestWait() { while ( g_nThreads < NUM_THREADS ) { ThreadSleep( 0 ); } g_bStart = true; while ( g_nThreads > 0 ) { ThreadSleep( 0 ); } } void TestEnd( bool bExpectEmpty = true ) { ValidateBuckets(); if ( g_nPops != g_nPushes ) { Msg( "FAIL: Not all items popped\n" ); return; } if ( g_pTestOps->Validate() ) { if ( !bExpectEmpty || g_pTestOps->IsEmpty() ) { Msg("pass\n"); } else { Msg("FAIL: !IsEmpty()\n"); } } else { Msg("FAIL: !Validate()\n"); } } //-------------------------------------------------- // // Shared Tests for CTSQueue and CTSList // //-------------------------------------------------- void PushPopTest() { Msg( "%s test: single thread push/pop, in order... ", g_pListType ); ClearBuckets(); g_nTested = 0; int value; while ( g_nTested < NUM_TEST ) { value = g_nTested++; g_pTestOps->Push( value ); IncBucket( value ); } g_pTestOps->Validate(); while ( g_pTestOps->Pop( &value ) ) { DecBucket( value ); } TestEnd(); } void PushPopInterleavedTestGuts() { int value; for (;;) { bool bPush = ( rand() % 2 == 0 ); if ( bPush && ( value = g_nTested++ ) < NUM_TEST ) { g_pTestOps->Push( value ); IncBucket( value ); } else if ( g_pTestOps->Pop( &value ) ) { DecBucket( value ); } else { if ( g_nTested >= NUM_TEST ) { break; } } } } void PushPopInterleavedTest() { Msg( "%s test: single thread push/pop, interleaved... ", g_pListType ); srand( Plat_MSTime() ); g_nTested = 0; ClearBuckets(); PushPopInterleavedTestGuts(); TestEnd(); } uintp PushPopInterleavedTestThreadFunc( void * ) { ThreadSetDebugName( "PushPopThread" ); g_nThreads++; while ( !g_bStart ) { ThreadSleep( 0 ); } PushPopInterleavedTestGuts(); g_nThreads--; return 0; } void STPushMTPop( bool bDistribute ) { Msg( "%s test: single thread push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); TestStart(); ThreadHandle_t hPush = CreateSimpleThread( &PushThreadFunc, NULL ); ThreadHandle_t *arrPops = CreateTestThreads( PopThreadFunc, NUM_THREADS - 1, ( bDistribute ) ? NUM_PROCESSORS : 0 ); TestWait(); TestEnd(); JoinTestThreads( arrPops ); ThreadJoin( hPush ); ReleaseThreadHandle( hPush ); } void MTPushSTPop( bool bDistribute ) { Msg( "%s test: multithread push, single thread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); TestStart(); ThreadHandle_t hPop = CreateSimpleThread( &PopThreadFunc, NULL ); ThreadHandle_t* arrPushes = CreateTestThreads( PushThreadFunc, NUM_THREADS - 1, ( bDistribute ) ? NUM_PROCESSORS : 0 ); TestWait(); TestEnd(); JoinTestThreads( arrPushes ); ThreadJoin( hPop ); ReleaseThreadHandle( hPop ); } void MTPushMTPop( bool bDistribute ) { Msg( "%s test: multithread push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); TestStart(); int ct = 0; ThreadHandle_t *threadHandles = (ThreadHandle_t *)stackalloc( NUM_THREADS * sizeof(ThreadHandle_t) ); int nHandles = 0; for ( int i = 0; i < NUM_THREADS / 2 ; i++ ) { ThreadHandle_t hThread = CreateSimpleThread( &PopThreadFunc, NULL ); threadHandles[nHandles++] = hThread; if ( bDistribute ) { int32 mask = 1 << (ct++ % NUM_PROCESSORS); ThreadSetAffinity( hThread, mask ); } } for ( int i = 0; i < NUM_THREADS / 2 ; i++ ) { ThreadHandle_t hThread = CreateSimpleThread( &PushThreadFunc, NULL ); threadHandles[nHandles++] = hThread; if ( bDistribute ) { int32 mask = 1 << (ct++ % NUM_PROCESSORS); ThreadSetAffinity( hThread, mask ); } } TestWait(); TestEnd(); for ( int i = 0; i < nHandles; i++ ) { ReleaseThreadHandle( threadHandles[i] ); } } void MTPushPopPopInterleaved( bool bDistribute ) { Msg( "%s test: multithread interleaved push/pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); srand( Plat_MSTime() ); TestStart(); ThreadHandle_t * arrPushPops = CreateTestThreads( &PushPopInterleavedTestThreadFunc, NUM_THREADS, ( bDistribute ) ? NUM_PROCESSORS : 0 ); TestWait(); TestEnd(); JoinTestThreads( arrPushPops ); } void MTPushSeqPop( bool bDistribute ) { Msg( "%s test: multithread push, sequential pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); TestStart(); ThreadHandle_t * arrPushes = CreateTestThreads( PushThreadFunc, NUM_THREADS, ( bDistribute ) ? NUM_PROCESSORS : 0 ); TestWait(); int ignored; g_pTestOps->Validate(); int nPopped = 0; while ( g_pTestOps->Pop( &ignored ) ) { nPopped++; } if ( nPopped != NUM_TEST ) { Msg( "Pops != pushes?\n" ); DebuggerBreakIfDebugging(); } TestEnd(); JoinTestThreads( arrPushes ); } void SeqPushMTPop( bool bDistribute ) { Msg( "%s test: sequential push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); TestStart(); while ( g_nTested++ < NUM_TEST ) { g_pTestOps->Push( g_nTested ); } ThreadHandle_t * arrPops = CreateTestThreads( PopThreadFunc, NUM_THREADS, ( bDistribute ) ? NUM_PROCESSORS : 0 ); TestWait(); TestEnd(); JoinTestThreads( arrPops ); } #ifdef _PS3 void TestThreadProc( uint64_t id ) { printf( "(TS)Hello from PPU thread %lld @%p\n", id, &id ); sys_ppu_thread_exit( id ); } uintp TestThreadProc2( void *p ) { printf( "(TS)Hello from PPU thread %lld @%p\n", (int64)p, &p ); return (uintp)p; } #endif void TestThreads() { #ifdef _PS3 printf("(TS)testing threads\n"); const int numThreads = 40; ThreadHandle_t * arrTests = CreateTestThreads( TestThreadProc2, numThreads, false ); JoinTestThreads( arrTests ); #endif } } void RunSharedTests( int nTests ) { using namespace TSListTests; TestThreads(); const CPUInformation &pi = GetCPUInformation(); NUM_PROCESSORS = pi.m_nLogicalProcessors; MAX_THREADS = NUM_PROCESSORS * 2; g_pTestBuckets = new int[NUM_TEST]; while ( nTests-- ) { for ( NUM_THREADS = 2; NUM_THREADS <= MAX_THREADS; NUM_THREADS *= 2) { Msg( "\nTesting %d threads:\n", NUM_THREADS ); PushPopTest(); PushPopInterleavedTest(); SeqPushMTPop( false ); STPushMTPop( false ); MTPushSeqPop( false ); MTPushSTPop( false ); MTPushMTPop( false ); MTPushPopPopInterleaved( false ); if ( NUM_PROCESSORS > 1 ) { SeqPushMTPop( true ); STPushMTPop( true ); MTPushSeqPop( true ); MTPushSTPop( true ); MTPushMTPop( true ); MTPushPopPopInterleaved( true ); } } } delete[] g_pTestBuckets; } bool RunTSListTests( int nListSize, int nTests ) { using namespace TSListTests; NUM_TEST = nListSize; #ifdef USE_NATIVE_SLIST #ifdef _WIN64 int maxSize = 65536; // FIXME: How should this be computed? #else int maxSize = ( 1 << (sizeof( ((TSLHead_t *)(0))->Depth ) * 8) ) - 1; #endif #else int maxSize = ( 1 << (sizeof( ((TSLHead_t *)(0))->value.Depth ) * 8) ) - 1; #endif if ( NUM_TEST > maxSize ) { Msg( "TSList cannot hold more that %d nodes\n", maxSize ); return false; } g_pTestOps = &g_ListOps; g_pListType = "CTSList"; RunSharedTests( nTests ); Msg("Tests done, purging test memory..." ); g_TestList.Purge(); Msg( "done\n"); return true; } bool RunTSQueueTests( int nListSize, int nTests ) { using namespace TSListTests; NUM_TEST = nListSize; g_pTestOps = &g_QueueOps; g_pListType = "CTSQueue"; RunSharedTests( nTests ); Msg("Tests done, purging test memory..." ); g_TestQueue.Purge(); Msg( "done\n"); return true; }