|
|
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
//
// - hStartOfRace is a manual reset event that is signalled when
// all of the threads are supposed to cut loose and begin working
//
// - hEndOfRace is a manual reset event that is signalled once the end time
// has been retrieved and it is ok for the threads to exit
//
HANDLE hStartOfRace; HANDLE hEndOfRace;
#define MAX_THREADS 32
//
// - ThreadReadyDoneEvents are an array of autoclearing events. The threads
// initially signal these events once they have reached their start routines
// and are ready to being processing. Once they are done processing, they
// signal thier event to indicate that they are done processing.
//
// - ThreadHandles are an array of thread handles to the worker threads. The
// main thread waits on these to know when all of the threads have exited.
//
HANDLE ThreadReadyDoneEvents[MAX_THREADS]; HANDLE ThreadHandles[MAX_THREADS];
//
// Each thread has a THREAD_WORK structure. This contains the address
// of the cells that this thread is responsible for, and the number of
// cells it is supposed to process.
//
typedef struct _THREAD_WORK { PDWORD CellVector; DWORD NumberOfCells; DWORD RecalcResult; } THREAD_WORK, *PTHREAD_WORK;
THREAD_WORK ThreadWork[MAX_THREADS];
#define ONE_MB (1024*1024)
DWORD Mb = 4; DWORD NumberOfThreads = 1; DWORD ExpectedRecalcValue; DWORD ActualRecalcValue; DWORD ContentionValue; BOOL fMemoryContention;
DWORD WorkerThread(PVOID ThreadIndex);
int __cdecl main( int argc, char *argv[], char *envp[] ) { DWORD StartTicks, EndTicks; DWORD i; BOOL fShowUsage; char c, *p, *whocares; PDWORD CellVector; DWORD NumberOfDwords; DWORD DwordsPerThread; DWORD ThreadId; LPSTR Answer;
fShowUsage = FALSE; fMemoryContention = FALSE;
if (argc <= 1) { goto showUsage; }
while (--argc) { p = *++argv; if (*p == '/' || *p == '-') { while (c = *++p) switch (toupper( c )) { case '?': fShowUsage = TRUE; goto showUsage; break;
case 'M': if (!argc--) { fShowUsage = TRUE; goto showUsage; } argv++; Mb = strtoul(*argv,&whocares,10); break;
case 'C': fMemoryContention = TRUE; break;
case 'T': if (!argc--) { fShowUsage = TRUE; goto showUsage; } argv++; NumberOfThreads = strtoul(*argv,&whocares,10); if ( NumberOfThreads > MAX_THREADS ) { fShowUsage = TRUE; goto showUsage; } break;
default: fprintf( stderr, "MTBNCH: Invalid switch - /%c\n", c ); goto showUsage; break; } } }
showUsage: if ( fShowUsage ) { fprintf(stderr,"usage: MTBNCH\n" ); fprintf(stderr," [-?] display this message\n" ); fprintf(stderr," [-t n] use n threads for benchmark (less than 32)\n" ); fprintf(stderr," [-m n] use an n Mb spreadsheet size (default 4)\n" ); fprintf(stderr," [-c] cause memory contention on each loop iteration\n" ); ExitProcess(1); }
//
// Prepare the race events. These are manual reset events.
//
hStartOfRace = CreateEvent(NULL,TRUE,FALSE,NULL); hEndOfRace = CreateEvent(NULL,TRUE,FALSE,NULL);
if ( !hStartOfRace || !hEndOfRace ) { fprintf(stderr,"MTBNCH: Race Event Creation Failed\n"); ExitProcess(1); }
//
// Prepare the ready done events. These are auto clearing events
//
for(i=0; i<NumberOfThreads; i++ ) { ThreadReadyDoneEvents[i] = CreateEvent(NULL,FALSE,FALSE,NULL); if ( !ThreadReadyDoneEvents[i] ) { fprintf(stderr,"MTBNCH: Ready Done Event Creation Failed %d\n",GetLastError()); ExitProcess(1); } }
//
// Allocate and initialize the CellVector
//
CellVector = (PDWORD)VirtualAlloc(NULL,Mb*ONE_MB,MEM_COMMIT,PAGE_READWRITE); if ( !CellVector ) { fprintf(stderr,"MTBNCH: Cell Vector Allocation Failed %d\n",GetLastError()); ExitProcess(1); }
NumberOfDwords = (Mb*ONE_MB) / sizeof(DWORD); DwordsPerThread = NumberOfDwords / NumberOfThreads;
//
// Initialize the Cell Vector
//
for(i=0, ExpectedRecalcValue; i<NumberOfDwords; i++ ){ ExpectedRecalcValue += i; CellVector[i] = i; }
//
// Partition the work to the worker threads
//
for(i=0; i<NumberOfThreads; i++ ){ ThreadWork[i].CellVector = &CellVector[i*DwordsPerThread]; ThreadWork[i].NumberOfCells = DwordsPerThread; NumberOfDwords -= DwordsPerThread;
//
// If we have a remainder, give the remaining work to the last thread
//
if ( NumberOfDwords < DwordsPerThread ) { ThreadWork[i].NumberOfCells += NumberOfDwords; } }
//
// Create the worker threads
//
for(i=0; i<NumberOfThreads; i++ ) { ThreadHandles[i] = CreateThread( NULL, 0, WorkerThread, (PVOID)i, 0, &ThreadId ); if ( !ThreadHandles[i] ) { fprintf(stderr,"MTBNCH: Worker Thread Creation Failed %d\n",GetLastError()); ExitProcess(1); } }
//
// All of the worker threads will signal thier ready done event
// when they are idle and ready to proceed. Once all events have been
// set, then setting the hStartOfRaceEvent will begin the recalc
//
i = WaitForMultipleObjects( NumberOfThreads, ThreadReadyDoneEvents, TRUE, INFINITE );
if ( i == WAIT_FAILED ) { fprintf(stderr,"MTBNCH: Wait for threads to stabalize Failed %d\n",GetLastError()); ExitProcess(1); }
//
// Everthing is set to begin the recalc operation
//
StartTicks = GetTickCount(); if ( !SetEvent(hStartOfRace) ) { fprintf(stderr,"MTBNCH: SetEvent(hStartOfRace) Failed %d\n",GetLastError()); ExitProcess(1); }
//
// Now just wait for the recalc to complete
//
i = WaitForMultipleObjects( NumberOfThreads, ThreadReadyDoneEvents, TRUE, INFINITE );
if ( i == WAIT_FAILED ) { fprintf(stderr,"MTBNCH: Wait for threads to complete Failed %d\n",GetLastError()); ExitProcess(1); }
//
// Now pick up the individual recalc values
//
for(i=0, ActualRecalcValue = 0; i<NumberOfThreads; i++ ){ ActualRecalcValue += ThreadWork[i].RecalcResult; }
EndTicks = GetTickCount();
if ( fMemoryContention ) { if ( ContentionValue == (Mb*ONE_MB) / sizeof(DWORD) ) { if ( ActualRecalcValue == ExpectedRecalcValue ) { Answer = "Correct"; } else { Answer = "Recalc Failure"; } } else { Answer = "Contention Failure"; } } else { if ( ActualRecalcValue == ExpectedRecalcValue ) { Answer = "Correct"; } else { Answer = "Recalc Failure"; } }
fprintf(stdout,"MTBNCH: %d Thread Recalc complete in %dms, Answer = %s\n", NumberOfThreads, EndTicks-StartTicks, Answer );
ExitProcess(2); }
//
// The worker threads perform the recalc operation on their
// assigned cells. They begin by setting their ready done event
// to indicate that they are ready to begin the recalc. Then they
// wait until the hStartOfRace event is signaled. Once this occurs, they
// do their part of the recalc and when done they signal their ready done
// event and then wait on the hEndOfRaceEvent
//
DWORD WorkerThread( PVOID ThreadIndex ) {
DWORD Me; PDWORD MyCellVectorBase; PDWORD CurrentCellVector; DWORD MyRecalcValue; DWORD MyNumberOfCells; DWORD i; BOOL MemoryContention;
Me = (DWORD)ThreadIndex; MyRecalcValue = 0; MyCellVectorBase = ThreadWork[Me].CellVector; MyNumberOfCells = ThreadWork[Me].NumberOfCells; MemoryContention = fMemoryContention;
//
// Signal that I am ready to go
//
if ( !SetEvent(ThreadReadyDoneEvents[Me]) ) { fprintf(stderr,"MTBNCH: (1) SetEvent(ThreadReadyDoneEvent[%d]) Failed %d\n",Me,GetLastError()); ExitProcess(1); }
//
// Wait for the master to release us to do the recalc
//
i = WaitForSingleObject(hStartOfRace,INFINITE); if ( i == WAIT_FAILED ) { fprintf(stderr,"MTBNCH: Thread %d Wait for start of recalc Failed %d\n",Me,GetLastError()); ExitProcess(1); }
//
// perform the recalc operation
//
for (i=0, CurrentCellVector = MyCellVectorBase; i<MyNumberOfCells; i++ ) { MyRecalcValue += *CurrentCellVector++; if ( MemoryContention ) { InterlockedIncrement(&ContentionValue); } } ThreadWork[Me].RecalcResult = MyRecalcValue;
//
// Signal that I am done and then wait for further instructions
//
if ( !SetEvent(ThreadReadyDoneEvents[Me]) ) { fprintf(stderr,"MTBNCH: (2) SetEvent(ThreadReadyDoneEvent[%d]) Failed %d\n",Me,GetLastError()); ExitProcess(1); }
i = WaitForSingleObject(hEndOfRace,INFINITE); if ( i == WAIT_FAILED ) { fprintf(stderr,"MTBNCH: Thread %d Wait for end of recalc Failed %d\n",Me,GetLastError()); ExitProcess(1); }
return MyRecalcValue; }
|