diff --git a/part3/base.h b/part3/base.h index 9efc8a8..b4e615b 100644 --- a/part3/base.h +++ b/part3/base.h @@ -19,9 +19,10 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) -typedef float f32; -typedef double f64; -typedef uint8_t u8; +typedef float f32; +typedef double f64; +typedef bool b32; +typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; diff --git a/part3/listing_0065_haversine_formula.cpp b/part3/listing_0065_haversine_formula.cpp new file mode 100644 index 0000000..680c88f --- /dev/null +++ b/part3/listing_0065_haversine_formula.cpp @@ -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; +} diff --git a/part3/listing_0068_buffer.cpp b/part3/listing_0068_buffer.cpp new file mode 100644 index 0000000..93f99fa --- /dev/null +++ b/part3/listing_0068_buffer.cpp @@ -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 = {}; +} \ No newline at end of file diff --git a/part3/listing_0094_profiled_lookup_json_parser.cpp b/part3/listing_0094_profiled_lookup_json_parser.cpp new file mode 100644 index 0000000..56c8f57 --- /dev/null +++ b/part3/listing_0094_profiled_lookup_json_parser.cpp @@ -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; +} diff --git a/part3/listing_0100_bandwidth_profiler.cpp b/part3/listing_0100_bandwidth_profiler.cpp new file mode 100644 index 0000000..ab4e921 --- /dev/null +++ b/part3/listing_0100_bandwidth_profiler.cpp @@ -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); +} diff --git a/part3/listing_0101_read_bandwidth_main.cpp b/part3/listing_0101_read_bandwidth_main.cpp new file mode 100644 index 0000000..187a5a9 --- /dev/null +++ b/part3/listing_0101_read_bandwidth_main.cpp @@ -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 +#include +#include +#include +#include + +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; diff --git a/part3/listing_0102_read_overhead_test.cpp b/part3/listing_0102_read_overhead_test.cpp new file mode 100644 index 0000000..4747208 --- /dev/null +++ b/part3/listing_0102_read_overhead_test.cpp @@ -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 +#include +#include + +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"); + } + } +} diff --git a/part3/listing_0103_repetition_tester.cpp b/part3/listing_0103_repetition_tester.cpp new file mode 100644 index 0000000..75722fe --- /dev/null +++ b/part3/listing_0103_repetition_tester.cpp @@ -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; +} diff --git a/part3/listing_0104_read_overhead_main.cpp b/part3/listing_0104_read_overhead_main.cpp new file mode 100644 index 0000000..3cdd2df --- /dev/null +++ b/part3/listing_0104_read_overhead_main.cpp @@ -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 +#include +#include +#include +#include + +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; +} diff --git a/part3/listing_0105_read_overhead_reordered_main.cpp b/part3/listing_0105_read_overhead_reordered_main.cpp new file mode 100644 index 0000000..6662845 --- /dev/null +++ b/part3/listing_0105_read_overhead_reordered_main.cpp @@ -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 +#include +#include +#include +#include + +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; +} diff --git a/part3/listing_0106_mallocread_overhead_test.cpp b/part3/listing_0106_mallocread_overhead_test.cpp new file mode 100644 index 0000000..153af9a --- /dev/null +++ b/part3/listing_0106_mallocread_overhead_test.cpp @@ -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 +#include +#include + +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"); + } + } +} diff --git a/part3/listing_0107_mallocread_overhead_main.cpp b/part3/listing_0107_mallocread_overhead_main.cpp new file mode 100644 index 0000000..9fce453 --- /dev/null +++ b/part3/listing_0107_mallocread_overhead_main.cpp @@ -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 +#include +#include +#include +#include + +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; +} diff --git a/part3/listing_0108_platform_metrics.cpp b/part3/listing_0108_platform_metrics.cpp new file mode 100644 index 0000000..f0e8bd2 --- /dev/null +++ b/part3/listing_0108_platform_metrics.cpp @@ -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 +#include +#include + +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 +#include + +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; +} diff --git a/part3/listing_0109_pagefault_repetition_tester.cpp b/part3/listing_0109_pagefault_repetition_tester.cpp new file mode 100644 index 0000000..2aa69c8 --- /dev/null +++ b/part3/listing_0109_pagefault_repetition_tester.cpp @@ -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; +} diff --git a/part3/listing_0110_pagefault_overhead_test.cpp b/part3/listing_0110_pagefault_overhead_test.cpp new file mode 100644 index 0000000..099d60f --- /dev/null +++ b/part3/listing_0110_pagefault_overhead_test.cpp @@ -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); + } +} diff --git a/part3/listing_0111_pagefault_overhead_main.cpp b/part3/listing_0111_pagefault_overhead_main.cpp new file mode 100644 index 0000000..cc431c5 --- /dev/null +++ b/part3/listing_0111_pagefault_overhead_main.cpp @@ -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 +#include +#include +#include +#include + +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; +} diff --git a/part3/listing_0112_os_fault_counter_main.cpp b/part3/listing_0112_os_fault_counter_main.cpp new file mode 100644 index 0000000..3bbfeea --- /dev/null +++ b/part3/listing_0112_os_fault_counter_main.cpp @@ -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 +#include + +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; +} diff --git a/part3/listing_0113_os_fault_counter_backward_main.cpp b/part3/listing_0113_os_fault_counter_backward_main.cpp new file mode 100644 index 0000000..cb396f7 --- /dev/null +++ b/part3/listing_0113_os_fault_counter_backward_main.cpp @@ -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 +#include + +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; +} diff --git a/part3/repetition_tester.c b/part3/repetition_tester.c index a701c6f..609c192 100644 --- a/part3/repetition_tester.c +++ b/part3/repetition_tester.c @@ -7,7 +7,7 @@ #include #include "base.h" -#include "listing_0074_platform_metrics.cpp" +#include "listing_0108_platform_metrics.cpp" #include "base.c" #include "repetition_tester.h" @@ -63,18 +63,20 @@ void RepTester_Error(RepTester *tester, Str8 msg) void RepTester_BeginTime(RepTester *tester) { 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) { 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) { - 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) @@ -92,18 +94,42 @@ 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); - printf("\n"); + u64 test_count = value.e[RepTesterValueType_TestCount]; + f64 divisor = test_count ? (f64)test_count : 1; - PrintTime(STR8("Max"), (f64)results.max_time, cpu_timer_freq, byte_count); - printf("\n"); + f64 avg_values[ARRAY_UCOUNT(value.e)]; + for (u64 index = 0; index < ARRAY_UCOUNT(value.e); index++) + avg_values[index] = value.e[index] / divisor; - if (results.test_count) { - PrintTime(STR8("Avg"), (f64)results.total_time / (f64)results.test_count, cpu_timer_freq, byte_count); - printf("\n"); + printf("%.*s: %.0f", STR8_FMT(label), avg_values[RepTesterValueType_CPUTimer]); + if (cpu_timer_freq) { + 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) @@ -114,29 +140,34 @@ bool RepTester_IsTesting(RepTester *tester) if (tester->open_block_count != tester->close_block_count) 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")); if (tester->mode == RepTesterMode_Testing) { - RepTesterResults *results = &tester->results; - u64 elapsed_time = tester->time_accumulated_on_this_test; - results->test_count += 1; - results->total_time += elapsed_time; - results->max_time = MAX(results->max_time, elapsed_time); + RepTesterResults *results = &tester->results; + accum.e[RepTesterValueType_TestCount] = 1; - if (results->min_time > elapsed_time) { - results->min_time = elapsed_time; + for (u64 index = 0; index < ARRAY_UCOUNT(accum.e); index++) { + 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 tester->start_time = current_time; - PrintTime(STR8("Min"), (f64)results->min_time, tester->cpu_timer_freq, tester->desired_bytes_read); - printf(" \r"); + RepTester_PrintValue(STR8("Min"), results->min, tester->cpu_timer_freq); + printf(" \r"); } - tester->open_block_count = 0; - tester->close_block_count = 0; - tester->time_accumulated_on_this_test = 0; - tester->total_bytes_read = 0; + tester->open_block_count = 0; + tester->close_block_count = 0; + tester->accumulated_on_this_test = (RepTesterValue){}; } } @@ -144,7 +175,7 @@ bool RepTester_IsTesting(RepTester *tester) tester->mode = RepTesterMode_Complete; 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 struct TestFunction { Str8 name; @@ -292,17 +337,20 @@ int main(int argc, char const **argv) return -1; } + InitializeOSMetrics(); + TestFunction test_functions[] = { - {STR8("fread"), ReadWithFRead}, - {STR8("_read"), ReadWithRead}, - {STR8("ReadFile"), ReadWithReadFile}, + {STR8("WriteToAllBytes"), WriteToAllBytes}, + {STR8("fread"), ReadWithFRead}, + {STR8("_read"), ReadWithRead}, + {STR8("ReadFile"), ReadWithReadFile}, }; RepTester testers[ARRAY_UCOUNT(test_functions)][AllocType_Count] = {}; u64 cpu_timer_freq = EstimateCPUTimerFreq(); for (u64 index = 0; index != UINT64_MAX; 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]; TestFunction test_func = test_functions[func_index]; @@ -311,10 +359,10 @@ int main(int argc, char const **argv) STR8_FMT(test_func.name)); if (tester->mode == RepTesterMode_Nil) { - tester->mode = RepTesterMode_Testing; - tester->desired_bytes_read = args.dest.size; - tester->cpu_timer_freq = cpu_timer_freq; - tester->results.min_time = (u64)-1; + tester->mode = RepTesterMode_Testing; + tester->desired_bytes_read = args.dest.size; + tester->cpu_timer_freq = cpu_timer_freq; + tester->results.min.e[RepTesterValueType_CPUTimer] = (u64)-1; } else if (tester->mode == RepTesterMode_Complete) { tester->mode = RepTesterMode_Testing; diff --git a/part3/repetition_tester.h b/part3/repetition_tester.h index e7250bf..e81f84c 100644 --- a/part3/repetition_tester.h +++ b/part3/repetition_tester.h @@ -5,11 +5,22 @@ typedef enum RepTesterMode { RepTesterMode_Complete, } 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 { - u64 test_count; - u64 total_time; - u64 max_time; - u64 min_time; + RepTesterValue total; + RepTesterValue max; + RepTesterValue min; } RepTesterResults; typedef struct RepTester { @@ -17,12 +28,12 @@ typedef struct RepTester { u32 open_block_count; u32 close_block_count; u64 cpu_timer_freq; - RepTesterResults results; - size_t total_bytes_read; + RepTesterResults results; + RepTesterValue accumulated_on_this_test; + size_t desired_bytes_read; - u64 time_accumulated_on_this_test; u64 start_time; u64 run_duration; } RepTester; diff --git a/project.rdbg b/project.rdbg index 329f38d..a18fc96 100644 Binary files a/project.rdbg and b/project.rdbg differ