perfaware/part3: Complete 61 Page faults (aggregate faults in each test)
This commit is contained in:
parent
1b21b7c533
commit
a9c9ae6371
@ -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;
|
||||
|
53
part3/listing_0065_haversine_formula.cpp
Normal file
53
part3/listing_0065_haversine_formula.cpp
Normal 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;
|
||||
}
|
72
part3/listing_0068_buffer.cpp
Normal file
72
part3/listing_0068_buffer.cpp
Normal 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 = {};
|
||||
}
|
516
part3/listing_0094_profiled_lookup_json_parser.cpp
Normal file
516
part3/listing_0094_profiled_lookup_json_parser.cpp
Normal 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;
|
||||
}
|
194
part3/listing_0100_bandwidth_profiler.cpp
Normal file
194
part3/listing_0100_bandwidth_profiler.cpp
Normal 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);
|
||||
}
|
186
part3/listing_0101_read_bandwidth_main.cpp
Normal file
186
part3/listing_0101_read_bandwidth_main.cpp
Normal 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;
|
151
part3/listing_0102_read_overhead_test.cpp
Normal file
151
part3/listing_0102_read_overhead_test.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
211
part3/listing_0103_repetition_tester.cpp
Normal file
211
part3/listing_0103_repetition_tester.cpp
Normal 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;
|
||||
}
|
116
part3/listing_0104_read_overhead_main.cpp
Normal file
116
part3/listing_0104_read_overhead_main.cpp
Normal 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)
|
||||