perfaware/part3: Complete 61 Page faults (aggregate faults in each test)

This commit is contained in:
doylet 2024-03-05 21:45:07 +11:00
parent 1b21b7c533
commit a9c9ae6371
21 changed files with 2788 additions and 45 deletions

View File

@ -21,6 +21,7 @@
typedef float f32; typedef float f32;
typedef double f64; typedef double f64;
typedef bool b32;
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
typedef uint32_t u32; typedef uint32_t u32;

View File

@ -0,0 +1,53 @@
/* ========================================================================
(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 65
======================================================================== */
static f64 Square(f64 A)
{
f64 Result = (A*A);
return Result;
}
static f64 RadiansFromDegrees(f64 Degrees)
{
f64 Result = 0.01745329251994329577 * Degrees;
return Result;
}
// NOTE(casey): EarthRadius is generally expected to be 6372.8
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
{
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
question on which these homework exercises are loosely based.
*/
f64 lat1 = Y0;
f64 lat2 = Y1;
f64 lon1 = X0;
f64 lon2 = X1;
f64 dLat = RadiansFromDegrees(lat2 - lat1);
f64 dLon = RadiansFromDegrees(lon2 - lon1);
lat1 = RadiansFromDegrees(lat1);
lat2 = RadiansFromDegrees(lat2);
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
f64 c = 2.0*asin(sqrt(a));
f64 Result = EarthRadius * c;
return Result;
}

View File

@ -0,0 +1,72 @@
/* ========================================================================
(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 68
======================================================================== */
struct buffer
{
size_t Count;
u8 *Data;
};
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
static b32 IsInBounds(buffer Source, u64 At)
{
b32 Result = (At < Source.Count);
return Result;
}
static b32 AreEqual(buffer A, buffer B)
{
if(A.Count != B.Count)
{
return false;
}
for(u64 Index = 0; Index < A.Count; ++Index)
{
if(A.Data[Index] != B.Data[Index])
{
return false;
}
}
return true;
}
static buffer AllocateBuffer(size_t Count)
{
buffer Result = {};
Result.Data = (u8 *)malloc(Count);
if(Result.Data)
{
Result.Count = Count;
}
else
{
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
}
return Result;
}
static void FreeBuffer(buffer *Buffer)
{
if(Buffer->Data)
{
free(Buffer->Data);
}
*Buffer = {};
}

View File

@ -0,0 +1,516 @@
/* ========================================================================
(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 94
======================================================================== */
enum json_token_type
{
Token_end_of_stream,
Token_error,
Token_open_brace,
Token_open_bracket,
Token_close_brace,
Token_close_bracket,
Token_comma,
Token_colon,
Token_string_literal,
Token_number,
Token_true,
Token_false,
Token_null,
Token_count,
};
struct json_token
{
json_token_type Type;
buffer Value;
};
struct json_element
{
buffer Label;
buffer Value;
json_element *FirstSubElement;
json_element *NextSibling;
};
struct json_parser
{
buffer Source;
u64 At;
b32 HadError;
};
static b32 IsJSONDigit(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val >= '0') && (Val <= '9'));
}
return Result;
}
static b32 IsJSONWhitespace(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
}
return Result;
}
static b32 IsParsing(json_parser *Parser)
{
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
return Result;
}
static void Error(json_parser *Parser, json_token Token, char const *Message)
{
Parser->HadError = true;
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
}
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
{
if((Source.Count - *At) >= KeywordRemaining.Count)
{
buffer Check = Source;
Check.Data += *At;
Check.Count = KeywordRemaining.Count;
if(AreEqual(Check, KeywordRemaining))
{
Result->Type = Type;
Result->Value.Count += KeywordRemaining.Count;
*At += KeywordRemaining.Count;
}
}
}
static json_token GetJSONToken(json_parser *Parser)
{
json_token Result = {};
buffer Source = Parser->Source;
u64 At = Parser->At;
while(IsJSONWhitespace(Source, At))
{
++At;
}
if(IsInBounds(Source, At))
{
Result.Type = Token_error;
Result.Value.Count = 1;
Result.Value.Data = Source.Data + At;
u8 Val = Source.Data[At++];
switch(Val)
{
case '{': {Result.Type = Token_open_brace;} break;
case '[': {Result.Type = Token_open_bracket;} break;
case '}': {Result.Type = Token_close_brace;} break;
case ']': {Result.Type = Token_close_bracket;} break;
case ',': {Result.Type = Token_comma;} break;
case ':': {Result.Type = Token_colon;} break;
case 'f':
{
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
} break;
case 'n':
{
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
} break;
case 't':
{
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
} break;
case '"':
{
Result.Type = Token_string_literal;
u64 StringStart = At;
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
{
if(IsInBounds(Source, (At + 1)) &&
(Source.Data[At] == '\\') &&
(Source.Data[At + 1] == '"'))
{
// NOTE(casey): Skip escaped quotation marks
++At;
}
++At;
}
Result.Value.Data = Source.Data + StringStart;
Result.Value.Count = At - StringStart;
if(IsInBounds(Source, At))
{
++At;
}
} break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
u64 Start = At - 1;
Result.Type = Token_number;
// NOTE(casey): Move past a leading negative sign if one exists
if((Val == '-') && IsInBounds(Source, At))
{
Val = Source.Data[At++];
}
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
if(Val != '0')
{
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
{
++At;
}
while(IsJSONDigit(Source, At))
{
++At;
}
}
Result.Value.Count = At - Start;
} break;
default:
{
} break;
}
}
Parser->At = At;
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
{
b32 Valid = true;
json_element *SubElement = 0;
if(Value.Type == Token_open_bracket)
{
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
}
else if(Value.Type == Token_open_brace)
{
SubElement = ParseJSONList(Parser, Token_close_brace, true);
}
else if((Value.Type == Token_string_literal) ||
(Value.Type == Token_true) ||
(Value.Type == Token_false) ||
(Value.Type == Token_null) ||
(Value.Type == Token_number))
{
// NOTE(casey): Nothing to do here, since there is no additional data
}
else
{
Valid = false;
}
json_element *Result = 0;
if(Valid)
{
Result = (json_element *)malloc(sizeof(json_element));
Result->Label = Label;
Result->Value = Value.Value;
Result->FirstSubElement = SubElement;
Result->NextSibling = 0;
}
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
{
json_element *FirstElement = {};
json_element *LastElement = {};
while(IsParsing(Parser))
{
buffer Label = {};
json_token Value = GetJSONToken(Parser);
if(HasLabels)
{
if(Value.Type == Token_string_literal)
{
Label = Value.Value;
json_token Colon = GetJSONToken(Parser);
if(Colon.Type == Token_colon)
{
Value = GetJSONToken(Parser);
}
else
{
Error(Parser, Colon, "Expected colon after field name");
}
}
else if(Value.Type != EndType)
{
Error(Parser, Value, "Unexpected token in JSON");
}
}
json_element *Element = ParseJSONElement(Parser, Label, Value);
if(Element)
{
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
}
else if(Value.Type == EndType)
{
break;
}
else
{
Error(Parser, Value, "Unexpected token in JSON");
}
json_token Comma = GetJSONToken(Parser);
if(Comma.Type == EndType)
{
break;
}
else if(Comma.Type != Token_comma)
{
Error(Parser, Comma, "Unexpected token in JSON");
}
}
return FirstElement;
}
static json_element *ParseJSON(buffer InputJSON)
{
TimeFunction;
json_parser Parser = {};
Parser.Source = InputJSON;
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
return Result;
}
static void FreeJSON(json_element *Element)
{
while(Element)
{
json_element *FreeElement = Element;
Element = Element->NextSibling;
FreeJSON(FreeElement->FirstSubElement);
free(FreeElement);
}
}
static json_element *LookupElement(json_element *Object, buffer ElementName)
{
json_element *Result = 0;
if(Object)
{
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
{
if(AreEqual(Search->Label, ElementName))
{
Result = Search;
break;
}
}
}
return Result;
}
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 1.0;
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
{
Result = -1.0;
++At;
}
*AtResult = At;
return Result;
}
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 0.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Result = 10.0*Result + (f64)Char;
++At;
}
else
{
break;
}
}
*AtResult = At;
return Result;
}
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
{
f64 Result = 0.0;
json_element *Element = LookupElement(Object, ElementName);
if(Element)
{
buffer Source = Element->Value;
u64 At = 0;
f64 Sign = ConvertJSONSign(Source, &At);
f64 Number = ConvertJSONNumber(Source, &At);
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
f64 C = 1.0 / 10.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Number = Number + C*(f64)Char;
C *= 1.0 / 10.0;
++At;
}
else
{
break;
}
}
}
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
{
++At;
}
f64 ExponentSign = ConvertJSONSign(Source, &At);
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
Number *= pow(10.0, Exponent);
}
Result = Sign*Number;
}
return Result;
}
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
{
TimeFunction;
u64 PairCount = 0;
json_element *JSON = ParseJSON(InputJSON);
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
if(PairsArray)
{
TimeBlock("Lookup and Convert");
for(json_element *Element = PairsArray->FirstSubElement;
Element && (PairCount < MaxPairCount);
Element = Element->NextSibling)
{
haversine_pair *Pair = Pairs + PairCount++;
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
}
}
{
TimeBlock("FreeJSON");
FreeJSON(JSON);
}
return PairCount;
}

View File

@ -0,0 +1,194 @@
/* ========================================================================
(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 100
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#ifndef READ_BLOCK_TIMER
#define READ_BLOCK_TIMER ReadCPUTimer
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
u64 ProcessedByteCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_, u64 ByteCount)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
Anchor->ProcessedByteCount += ByteCount;
GlobalProfilerParent = AnchorIndex;
StartTSC = READ_BLOCK_TIMER();
}
~profile_block(void)
{
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
Parent->TSCElapsedExclusive -= Elapsed;
Anchor->TSCElapsedExclusive += Elapsed;
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
++Anchor->HitCount;
/* NOTE(casey): This write happens every time solely because there is no
straightforward way in C++ to have the same ease-of-use. In a better programming
language, it would be simple to have the anchor points gathered and labeled at compile
time, and this repetative write would be eliminated. */
Anchor->Label = Label;
}
char const *Label;
u64 OldTSCElapsedInclusive;
u64 StartTSC;
u32 ParentIndex;
u32 AnchorIndex;
};
#define NameConcat2(A, B) A##B
#define NameConcat(A, B) NameConcat2(A, B)
#define TimeBandwidth(Name, ByteCount) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1, ByteCount)
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, u64 TimerFreq, profile_anchor *Anchor)
{
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")");
if(Anchor->ProcessedByteCount)
{
f64 Megabyte = 1024.0f*1024.0f;
f64 Gigabyte = Megabyte*1024.0f;
f64 Seconds = (f64)Anchor->TSCElapsedInclusive / (f64)TimerFreq;
f64 BytesPerSecond = (f64)Anchor->ProcessedByteCount / Seconds;
f64 Megabytes = (f64)Anchor->ProcessedByteCount / (f64)Megabyte;
f64 GigabytesPerSecond = BytesPerSecond / Gigabyte;
printf(" %.3fmb at %.2fgb/s", Megabytes, GigabytesPerSecond);
}
printf("\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed, u64 TimerFreq)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, TimerFreq, Anchor);
}
}
}
#else
#define TimeBandwidth(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeBlock(Name) TimeBandwidth(Name, 0)
#define TimeFunction TimeBlock(__func__)
static u64 EstimateBlockTimerFreq(void)
{
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 BlockStart = READ_BLOCK_TIMER();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 BlockEnd = READ_BLOCK_TIMER();
u64 BlockElapsed = BlockEnd - BlockStart;
u64 BlockFreq = 0;
if(OSElapsed)
{
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
}
return BlockFreq;
}
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
u64 TimerFreq = EstimateBlockTimerFreq();
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(TimerFreq)
{
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
}
PrintAnchorData(TotalTSCElapsed, TimerFreq);
}

View File

@ -0,0 +1,186 @@
/* ========================================================================
(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 101
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0100_bandwidth_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0094_profiled_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
TimeBandwidth("fread", Result.Count);
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeBandwidth(__func__, PairCount*sizeof(haversine_pair));
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;

View File

@ -0,0 +1,151 @@
/* ========================================================================
(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 102
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
struct read_parameters
{
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}

View File

@ -0,0 +1,211 @@
/* ========================================================================
(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;
}

View File

@ -0,0 +1,116 @@
/* ========================================================================
(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 104
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}

View File

@ -0,0 +1,116 @@
/* ========================================================================
(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 105
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"ReadFile", ReadViaReadFile},
{"_read", ReadViaRead},
{"fread", ReadViaFRead},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}

View File

@ -0,0 +1,219 @@
/* ========================================================================
(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 106
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
enum allocation_type
{
AllocType_none,
AllocType_malloc,
AllocType_Count,
};
struct read_parameters
{
allocation_type AllocType;
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static char const *DescribeAllocationType(allocation_type AllocType)
{
char const *Result;
switch(AllocType)
{
case AllocType_none: {Result = "";} break;
case AllocType_malloc: {Result = "malloc";} break;
default : {Result = "UNKNOWN";} break;
}
return Result;
}
static void HandleAllocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
*Buffer = AllocateBuffer(Params->Dest.Count);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void HandleDeallocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
FreeBuffer(Buffer);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
HandleDeallocation(Params, &DestBuffer);
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}

View File

@ -0,0 +1,124 @@
/* ========================================================================
(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 107
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}

View File

@ -0,0 +1,145 @@
/* ========================================================================
(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 108
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
#include <psapi.h>
typedef struct os_metrics
{
b32 Initialized;
HANDLE ProcessHandle;
} os_metrics;
static os_metrics GlobalMetrics;
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
static u64 ReadOSPageFaultCount(void)
{
PROCESS_MEMORY_COUNTERS_EX MemoryCounters = {};
MemoryCounters.cb = sizeof(MemoryCounters);
GetProcessMemoryInfo(GlobalMetrics.ProcessHandle, (PROCESS_MEMORY_COUNTERS *)&MemoryCounters, sizeof(MemoryCounters));
u64 Result = MemoryCounters.PageFaultCount;
return Result;
}
static void InitializeOSMetrics(void)
{
if(!GlobalMetrics.Initialized)
{
GlobalMetrics.Initialized = true;
GlobalMetrics.ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
}
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
static u64 ReadOSPageFaultCount(void)
{
// NOTE(casey): The course materials are not tested on MacOS/Linux.
// This code was contributed to the public github. It may or may not work
// for your system.
struct rusage Usage = {};
getrusage(RUSAGE_SELF, &Usage);
// ru_minflt the number of page faults serviced without any I/O activity.
// ru_majflt the number of page faults serviced that required I/O activity.
u64 Result = Usage.ru_minflt + Usage.ru_majflt;
return Result;
}
static void InitializeOSMetrics(void)
{
}
#endif
/* NOTE(casey): This does not need to be "inline", it could just be "static"
because compilers will inline it anyway. But compilers will warn about
static functions that aren't used. So "inline" is just the simplest way
to tell them to stop complaining about that. */
inline u64 ReadCPUTimer(void)
{
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
// with one of their performance counter read instructions, depending
// on which ones are available on your platform.
return __rdtsc();
}
static u64 EstimateCPUTimerFreq(void)
{
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
u64 CPUFreq = 0;
if(OSElapsed)
{
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
}
return CPUFreq;
}

View File

@ -0,0 +1,240 @@
/* ========================================================================
(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;
}

View File

@ -0,0 +1,35 @@
/* ========================================================================
(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 110
======================================================================== */
static void WriteToAllBytes(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
BeginTime(Tester);
for(u64 Index = 0; Index < DestBuffer.Count; ++Index)
{
DestBuffer.Data[Index] = (u8)Index;
}
EndTime(Tester);
CountBytes(Tester, DestBuffer.Count);
HandleDeallocation(Params, &DestBuffer);
}
}

View File

@ -0,0 +1,127 @@
/* ========================================================================
(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 111
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0108_platform_metrics.cpp"
#include "listing_0109_pagefault_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
#include "listing_0110_pagefault_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"WriteToAllBytes", WriteToAllBytes},
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
InitializeOSMetrics();
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}

View File

@ -0,0 +1,89 @@
/* ========================================================================
(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 112
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}

View File

@ -0,0 +1,89 @@
/* ========================================================================
(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 113
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[TotalSize - 1 - Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}

View File

@ -7,7 +7,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include "base.h" #include "base.h"
#include "listing_0074_platform_metrics.cpp" #include "listing_0108_platform_metrics.cpp"
#include "base.c" #include "base.c"
#include "repetition_tester.h" #include "repetition_tester.h"
@ -63,18 +63,20 @@ void RepTester_Error(RepTester *tester, Str8 msg)
void RepTester_BeginTime(RepTester *tester) void RepTester_BeginTime(RepTester *tester)
{ {
tester->open_block_count++; tester->open_block_count++;
tester->time_accumulated_on_this_test -= ReadCPUTimer(); tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] -= ReadCPUTimer();
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] -= ReadOSPageFaultCount();
} }
void RepTester_EndTime(RepTester *tester) void RepTester_EndTime(RepTester *tester)
{ {
tester->close_block_count++; tester->close_block_count++;
tester->time_accumulated_on_this_test += ReadCPUTimer(); tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] += ReadCPUTimer();
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] += ReadOSPageFaultCount();
} }
void RepTester_CountBytes(RepTester *tester, size_t bytes_read) void RepTester_CountBytes(RepTester *tester, size_t bytes_read)
{ {
tester->total_bytes_read += bytes_read; tester->accumulated_on_this_test.e[RepTesterValueType_ByteCount] += bytes_read;
} }
static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_count) static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_count)
@ -92,20 +94,44 @@ static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_cou
} }
} }
static void RepTester_PrintResult(RepTesterResults results, u64 cpu_timer_freq, u64 byte_count) static void RepTester_PrintValue(Str8 label, RepTesterValue value, u64 cpu_timer_freq)
{ {
PrintTime(STR8("Min"), (f64)results.min_time, cpu_timer_freq, byte_count); u64 test_count = value.e[RepTesterValueType_TestCount];
printf("\n"); f64 divisor = test_count ? (f64)test_count : 1;
PrintTime(STR8("Max"), (f64)results.max_time, cpu_timer_freq, byte_count); f64 avg_values[ARRAY_UCOUNT(value.e)];
printf("\n"); for (u64 index = 0; index < ARRAY_UCOUNT(value.e); index++)
avg_values[index] = value.e[index] / divisor;
if (results.test_count) { printf("%.*s: %.0f", STR8_FMT(label), avg_values[RepTesterValueType_CPUTimer]);
PrintTime(STR8("Avg"), (f64)results.total_time / (f64)results.test_count, cpu_timer_freq, byte_count); if (cpu_timer_freq) {
printf("\n"); f64 seconds = avg_values[RepTesterValueType_CPUTimer] / cpu_timer_freq;
printf(" (%fms)", 1000.0f*seconds);
if (avg_values[RepTesterValueType_ByteCount] > 0) {
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 bandwidth = avg_values[RepTesterValueType_ByteCount] / (gigabyte * seconds);
printf(" %fgb/s", bandwidth);
} }
} }
if(avg_values[RepTesterValueType_MemPageFaults] > 0) {
printf(" PF: %0.4f (%0.4fk/fault)",
avg_values[RepTesterValueType_MemPageFaults],
avg_values[RepTesterValueType_ByteCount] / (avg_values[RepTesterValueType_MemPageFaults] * 1024.0));
}
}
static void RepTester_PrintResult(RepTesterResults results, u64 cpu_timer_freq)
{
RepTester_PrintValue(STR8("Min"), results.min, cpu_timer_freq);
printf("\n");
RepTester_PrintValue(STR8("Max"), results.max, cpu_timer_freq);
printf("\n");
RepTester_PrintValue(STR8("Avg"), results.total, cpu_timer_freq);
printf("\n");
}
bool RepTester_IsTesting(RepTester *tester) bool RepTester_IsTesting(RepTester *tester)
{ {
if (tester->mode == RepTesterMode_Testing) { if (tester->mode == RepTesterMode_Testing) {
@ -114,29 +140,34 @@ bool RepTester_IsTesting(RepTester *tester)
if (tester->open_block_count != tester->close_block_count) if (tester->open_block_count != tester->close_block_count)
RepTester_Error(tester, STR8("Unbalanced begin/end time")); RepTester_Error(tester, STR8("Unbalanced begin/end time"));
if (tester->total_bytes_read != tester->desired_bytes_read) RepTesterValue accum = tester->accumulated_on_this_test;
if (accum.e[RepTesterValueType_ByteCount] != tester->desired_bytes_read)
RepTester_Error(tester, STR8("Processed byte count mismatch")); RepTester_Error(tester, STR8("Processed byte count mismatch"));
if (tester->mode == RepTesterMode_Testing) { if (tester->mode == RepTesterMode_Testing) {
RepTesterResults *results = &tester->results; RepTesterResults *results = &tester->results;
u64 elapsed_time = tester->time_accumulated_on_this_test; accum.e[RepTesterValueType_TestCount] = 1;
results->test_count += 1;
results->total_time += elapsed_time;
results->max_time = MAX(results->max_time, elapsed_time);
if (results->min_time > elapsed_time) { for (u64 index = 0; index < ARRAY_UCOUNT(accum.e); index++) {
results->min_time = elapsed_time; results->total.e[index] += accum.e[index];
}
if (results->max.e[RepTesterValueType_CPUTimer] < accum.e[RepTesterValueType_CPUTimer]) {
results->max = accum;
}
if (results->min.e[RepTesterValueType_CPUTimer] > accum.e[RepTesterValueType_CPUTimer]) {
results->min = accum;
// NOTE: Reset the trial time when new min time is achieved // NOTE: Reset the trial time when new min time is achieved
tester->start_time = current_time; tester->start_time = current_time;
PrintTime(STR8("Min"), (f64)results->min_time, tester->cpu_timer_freq, tester->desired_bytes_read); RepTester_PrintValue(STR8("Min"), results->min, tester->cpu_timer_freq);
printf(" \r"); printf(" \r");
} }
tester->open_block_count = 0; tester->open_block_count = 0;
tester->close_block_count = 0; tester->close_block_count = 0;
tester->time_accumulated_on_this_test = 0; tester->accumulated_on_this_test = (RepTesterValue){};
tester->total_bytes_read = 0;
} }
} }
@ -144,7 +175,7 @@ bool RepTester_IsTesting(RepTester *tester)
tester->mode = RepTesterMode_Complete; tester->mode = RepTesterMode_Complete;
printf(" \r"); printf(" \r");
RepTester_PrintResult(tester->results, tester->cpu_timer_freq, tester->desired_bytes_read); RepTester_PrintResult(tester->results, tester->cpu_timer_freq);
} }
} }
@ -260,6 +291,20 @@ static void ReadWithReadFile(RepTester *tester, ReadArgs *args)
} }
} }
static void WriteToAllBytes(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
RepTester_BeginTime(tester);
for (u64 index = 0; index < buffer.size; index++)
buffer.data[index] = (u8)index;
RepTester_EndTime(tester);
RepTester_CountBytes(tester, buffer.size);
TestDealloc(args, &buffer);
}
}
typedef void TestFuncPtr(RepTester *tester, ReadArgs *args); typedef void TestFuncPtr(RepTester *tester, ReadArgs *args);
typedef struct TestFunction { typedef struct TestFunction {
Str8 name; Str8 name;
@ -292,7 +337,10 @@ int main(int argc, char const **argv)
return -1; return -1;
} }
InitializeOSMetrics();
TestFunction test_functions[] = { TestFunction test_functions[] = {
{STR8("WriteToAllBytes"), WriteToAllBytes},
{STR8("fread"), ReadWithFRead}, {STR8("fread"), ReadWithFRead},
{STR8("_read"), ReadWithRead}, {STR8("_read"), ReadWithRead},
{STR8("ReadFile"), ReadWithReadFile}, {STR8("ReadFile"), ReadWithReadFile},
@ -302,7 +350,7 @@ int main(int argc, char const **argv)
u64 cpu_timer_freq = EstimateCPUTimerFreq(); u64 cpu_timer_freq = EstimateCPUTimerFreq();
for (u64 index = 0; index != UINT64_MAX; index++) { for (u64 index = 0; index != UINT64_MAX; index++) {
for (u64 func_index = 0; func_index < ARRAY_UCOUNT(testers); func_index++) { for (u64 func_index = 0; func_index < ARRAY_UCOUNT(testers); func_index++) {
for (u64 alloc_index = AllocType_VirtualAlloc; alloc_index < AllocType_Count; alloc_index++) { for (u64 alloc_index = 0; alloc_index < AllocType_Count; alloc_index++) {
RepTester *tester = &testers[func_index][alloc_index]; RepTester *tester = &testers[func_index][alloc_index];
TestFunction test_func = test_functions[func_index]; TestFunction test_func = test_functions[func_index];
@ -314,7 +362,7 @@ int main(int argc, char const **argv)
tester->mode = RepTesterMode_Testing; tester->mode = RepTesterMode_Testing;
tester->desired_bytes_read = args.dest.size; tester->desired_bytes_read = args.dest.size;
tester->cpu_timer_freq = cpu_timer_freq; tester->cpu_timer_freq = cpu_timer_freq;
tester->results.min_time = (u64)-1; tester->results.min.e[RepTesterValueType_CPUTimer] = (u64)-1;
} else if (tester->mode == RepTesterMode_Complete) { } else if (tester->mode == RepTesterMode_Complete) {
tester->mode = RepTesterMode_Testing; tester->mode = RepTesterMode_Testing;

View File

@ -5,11 +5,22 @@ typedef enum RepTesterMode {
RepTesterMode_Complete, RepTesterMode_Complete,
} RepTesterMode; } RepTesterMode;
typedef enum RepTesterValueType {
RepTesterValueType_TestCount,
RepTesterValueType_CPUTimer,
RepTesterValueType_MemPageFaults,
RepTesterValueType_ByteCount,
RepTesterValueType_Count,
} RepTesterValueType;
typedef struct RepTesterValue {
u64 e[RepTesterValueType_Count];
} RepTesterValue;
typedef struct RepTesterResults { typedef struct RepTesterResults {
u64 test_count; RepTesterValue total;
u64 total_time; RepTesterValue max;
u64 max_time; RepTesterValue min;
u64 min_time;
} RepTesterResults; } RepTesterResults;
typedef struct RepTester { typedef struct RepTester {
@ -17,12 +28,12 @@ typedef struct RepTester {
u32 open_block_count; u32 open_block_count;
u32 close_block_count; u32 close_block_count;
u64 cpu_timer_freq; u64 cpu_timer_freq;
RepTesterResults results;
size_t total_bytes_read; RepTesterResults results;
RepTesterValue accumulated_on_this_test;
size_t desired_bytes_read; size_t desired_bytes_read;
u64 time_accumulated_on_this_test;
u64 start_time; u64 start_time;
u64 run_duration; u64 run_duration;
} RepTester; } RepTester;

Binary file not shown.