PerformanceAwareProgramming/part3/listing_0103_repetition_tester.cpp

212 lines
6.2 KiB
C++

/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 103
======================================================================== */
enum test_mode : u32
{
TestMode_Uninitialized,
TestMode_Testing,
TestMode_Completed,
TestMode_Error,
};
struct repetition_test_results
{
u64 TestCount;
u64 TotalTime;
u64 MaxTime;
u64 MinTime;
};
struct repetition_tester
{
u64 TargetProcessedByteCount;
u64 CPUTimerFreq;
u64 TryForTime;
u64 TestsStartedAt;
test_mode Mode;
b32 PrintNewMinimums;
u32 OpenBlockCount;
u32 CloseBlockCount;
u64 TimeAccumulatedOnThisTest;
u64 BytesAccumulatedOnThisTest;
repetition_test_results Results;
};
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
{
f64 Result = 0.0;
if(CPUTimerFreq)
{
Result = (CPUTime / (f64)CPUTimerFreq);
}
return Result;
}
static void PrintTime(char const *Label, f64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
printf("%s: %.0f", Label, CPUTime);
if(CPUTimerFreq)
{
f64 Seconds = SecondsFromCPUTime(CPUTime, CPUTimerFreq);
printf(" (%fms)", 1000.0f*Seconds);
if(ByteCount)
{
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 BestBandwidth = ByteCount / (Gigabyte * Seconds);
printf(" %fgb/s", BestBandwidth);
}
}
}
static void PrintTime(char const *Label, u64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime(Label, (f64)CPUTime, CPUTimerFreq, ByteCount);
}
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime("Min", (f64)Results.MinTime, CPUTimerFreq, ByteCount);
printf("\n");
PrintTime("Max", (f64)Results.MaxTime, CPUTimerFreq, ByteCount);
printf("\n");
if(Results.TestCount)
{
PrintTime("Avg", (f64)Results.TotalTime / (f64)Results.TestCount, CPUTimerFreq, ByteCount);
printf("\n");
}
}
static void Error(repetition_tester *Tester, char const *Message)
{
Tester->Mode = TestMode_Error;
fprintf(stderr, "ERROR: %s\n", Message);
}
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
{
if(Tester->Mode == TestMode_Uninitialized)
{
Tester->Mode = TestMode_Testing;
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
Tester->CPUTimerFreq = CPUTimerFreq;
Tester->PrintNewMinimums = true;
Tester->Results.MinTime = (u64)-1;
}
else if(Tester->Mode == TestMode_Completed)
{
Tester->Mode = TestMode_Testing;
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
{
Error(Tester, "TargetProcessedByteCount changed");
}
if(Tester->CPUTimerFreq != CPUTimerFreq)
{
Error(Tester, "CPU frequency changed");
}
}
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
Tester->TestsStartedAt = ReadCPUTimer();
}
static void BeginTime(repetition_tester *Tester)
{
++Tester->OpenBlockCount;
Tester->TimeAccumulatedOnThisTest -= ReadCPUTimer();
}
static void EndTime(repetition_tester *Tester)
{
++Tester->CloseBlockCount;
Tester->TimeAccumulatedOnThisTest += ReadCPUTimer();
}
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
{
Tester->BytesAccumulatedOnThisTest += ByteCount;
}
static b32 IsTesting(repetition_tester *Tester)
{
if(Tester->Mode == TestMode_Testing)
{
u64 CurrentTime = ReadCPUTimer();
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
{
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
{
Error(Tester, "Unbalanced BeginTime/EndTime");
}
if(Tester->BytesAccumulatedOnThisTest != Tester->TargetProcessedByteCount)
{
Error(Tester, "Processed byte count mismatch");
}
if(Tester->Mode == TestMode_Testing)
{
repetition_test_results *Results = &Tester->Results;
u64 ElapsedTime = Tester->TimeAccumulatedOnThisTest;
Results->TestCount += 1;
Results->TotalTime += ElapsedTime;
if(Results->MaxTime < ElapsedTime)
{
Results->MaxTime = ElapsedTime;
}
if(Results->MinTime > ElapsedTime)
{
Results->MinTime = ElapsedTime;
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
Tester->TestsStartedAt = CurrentTime;
if(Tester->PrintNewMinimums)
{
PrintTime("Min", Results->MinTime, Tester->CPUTimerFreq, Tester->BytesAccumulatedOnThisTest);
printf(" \r");
}
}
Tester->OpenBlockCount = 0;
Tester->CloseBlockCount = 0;
Tester->TimeAccumulatedOnThisTest = 0;
Tester->BytesAccumulatedOnThisTest = 0;
}
}
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
{
Tester->Mode = TestMode_Completed;
printf(" \r");
PrintResults(Tester->Results, Tester->CPUTimerFreq, Tester->TargetProcessedByteCount);
}
}
b32 Result = (Tester->Mode == TestMode_Testing);
return Result;
}