PerformanceAwareProgramming/part3/listing_0109_pagefault_repetition_tester.cpp

241 lines
7.0 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 109
======================================================================== */
enum test_mode : u32
{
TestMode_Uninitialized,
TestMode_Testing,
TestMode_Completed,
TestMode_Error,
};
enum repetition_value_type
{
RepValue_TestCount,
RepValue_CPUTimer,
RepValue_MemPageFaults,
RepValue_ByteCount,
RepValue_Count,
};
struct repetition_value
{
u64 E[RepValue_Count];
};
struct repetition_test_results
{
repetition_value Total;
repetition_value Min;
repetition_value Max;
};
struct repetition_tester
{
u64 TargetProcessedByteCount;
u64 CPUTimerFreq;
u64 TryForTime;
u64 TestsStartedAt;
test_mode Mode;
b32 PrintNewMinimums;
u32 OpenBlockCount;
u32 CloseBlockCount;
repetition_value AccumulatedOnThisTest;
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 PrintValue(char const *Label, repetition_value Value, u64 CPUTimerFreq)
{
u64 TestCount = Value.E[RepValue_TestCount];
f64 Divisor = TestCount ? (f64)TestCount : 1;
f64 E[RepValue_Count];
for(u32 EIndex = 0; EIndex < ArrayCount(E); ++EIndex)
{
E[EIndex] = (f64)Value.E[EIndex] / Divisor;
}
printf("%s: %.0f", Label, E[RepValue_CPUTimer]);
if(CPUTimerFreq)
{
f64 Seconds = SecondsFromCPUTime(E[RepValue_CPUTimer], CPUTimerFreq);
printf(" (%fms)", 1000.0f*Seconds);
if(E[RepValue_ByteCount] > 0)
{
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 Bandwidth = E[RepValue_ByteCount] / (Gigabyte * Seconds);
printf(" %fgb/s", Bandwidth);
}
}
if(E[RepValue_MemPageFaults] > 0)
{
printf(" PF: %0.4f (%0.4fk/fault)", E[RepValue_MemPageFaults], E[RepValue_ByteCount] / (E[RepValue_MemPageFaults] * 1024.0));
}
}
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq)
{
PrintValue("Min", Results.Min, CPUTimerFreq);
printf("\n");
PrintValue("Max", Results.Max, CPUTimerFreq);
printf("\n");
PrintValue("Avg", Results.Total, CPUTimerFreq);
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.Min.E[RepValue_CPUTimer] = (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;
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_MemPageFaults] -= ReadOSPageFaultCount();
Accum->E[RepValue_CPUTimer] -= ReadCPUTimer();
}
static void EndTime(repetition_tester *Tester)
{
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_CPUTimer] += ReadCPUTimer();
Accum->E[RepValue_MemPageFaults] += ReadOSPageFaultCount();
++Tester->CloseBlockCount;
}
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
{
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_ByteCount] += ByteCount;
}
static b32 IsTesting(repetition_tester *Tester)
{
if(Tester->Mode == TestMode_Testing)
{
repetition_value Accum = Tester->AccumulatedOnThisTest;
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(Accum.E[RepValue_ByteCount] != Tester->TargetProcessedByteCount)
{
Error(Tester, "Processed byte count mismatch");
}
if(Tester->Mode == TestMode_Testing)
{
repetition_test_results *Results = &Tester->Results;
Accum.E[RepValue_TestCount] = 1;
for(u32 EIndex = 0; EIndex < ArrayCount(Accum.E); ++EIndex)
{
Results->Total.E[EIndex] += Accum.E[EIndex];
}
if(Results->Max.E[RepValue_CPUTimer] < Accum.E[RepValue_CPUTimer])
{
Results->Max = Accum;
}
if(Results->Min.E[RepValue_CPUTimer] > Accum.E[RepValue_CPUTimer])
{
Results->Min = Accum;
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
Tester->TestsStartedAt = CurrentTime;
if(Tester->PrintNewMinimums)
{
PrintValue("Min", Results->Min, Tester->CPUTimerFreq);
printf(" \r");
}
}
Tester->OpenBlockCount = 0;
Tester->CloseBlockCount = 0;
Tester->AccumulatedOnThisTest = {};
}
}
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
{
Tester->Mode = TestMode_Completed;
printf(" \r");
PrintResults(Tester->Results, Tester->CPUTimerFreq);
}
}
b32 Result = (Tester->Mode == TestMode_Testing);
return Result;
}