6 Commits

Author SHA1 Message Date
doylet a9c9ae6371 perfaware/part3: Complete 61 Page faults (aggregate faults in each test) 2024-03-05 21:45:07 +11:00
doylet 1b21b7c533 Fix reading of exe file instead of passed in file 2024-03-05 00:17:14 +11:00
doylet 037e6e8d45 Add allocation type to the repetition test 2024-03-04 17:06:26 +11:00
doylet 4b1e8720e3 Add the repetition tester 2024-03-03 23:46:35 +11:00
doylet 65efb397ba Fix the build 2024-03-03 22:37:43 +11:00
doylet 8083a7c782 Prepare for part 3 2024-03-03 22:33:57 +11:00
40 changed files with 6456 additions and 91 deletions
+40 -27
View File
@@ -1,6 +1,6 @@
@echo off
REM Setup ==========================================================================================
REM Setup //////////////////////////////////////////////////////////////////////////////////////////
set script_dir_backslash=%~dp0
set script_dir=%script_dir_backslash:~0,-1%
set build_dir=%script_dir%\Build
@@ -12,15 +12,19 @@ if not exist %part1_build_dir% mkdir %part1_build_dir%
set part2_dir=%script_dir%\part2
set part2_build_dir=%build_dir%\part2
if not exist %part2_build_dir% mkdir %part2_build_dir%
goto :part2
REM Part 1 =========================================================================================
REM Build ==========================================================================================
set part3_dir=%script_dir%\part3
set part3_build_dir=%build_dir%\part3
if not exist %part3_build_dir% mkdir %part3_build_dir%
goto :part3
REM Part 1 /////////////////////////////////////////////////////////////////////////////////////////
REM Build //////////////////////////////////////////////////////////////////////////////////////////
pushd %part1_build_dir%
cl %part1_dir%\sim8086.c /W4 /WX /Z7 /nologo || exit /b 1
popd
REM Tests ==========================================================================================
REM Tests //////////////////////////////////////////////////////////////////////////////////////////
set listing_0037=listing_0037_single_register_mov
set part1_build_dir_listing_0037=%part1_build_dir%\%listing_0037%
@@ -30,7 +34,7 @@ copy /Y %part1_dir%\%listing_0037% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0037%_disassembled.asm
fc /B %part1_build_dir_listing_0037% %part1_build_dir_listing_0037%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0038=listing_0038_many_register_mov
set part1_build_dir_listing_0038=%part1_build_dir%\%listing_0038%
@@ -40,7 +44,7 @@ copy /Y %part1_dir%\%listing_0038% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0038%_disassembled.asm
fc /B %part1_build_dir_listing_0038% %part1_build_dir_listing_0038%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0039=listing_0039_more_movs
set part1_build_dir_listing_0039=%part1_build_dir%\%listing_0039%
@@ -50,7 +54,7 @@ copy /Y %part1_dir%\%listing_0039% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0039%_disassembled.asm
fc /B %part1_build_dir_listing_0039% %part1_build_dir_listing_0039%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0040=listing_0040_challenge_movs
set part1_build_dir_listing_0040=%part1_build_dir%\%listing_0040%
@@ -60,7 +64,7 @@ copy /Y %part1_dir%\%listing_0040% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0040%_disassembled.asm
fc /B %part1_build_dir_listing_0040% %part1_build_dir_listing_0040%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0041=listing_0041_add_sub_cmp_jnz
set part1_build_dir_listing_0041=%part1_build_dir%\%listing_0041%
@@ -70,7 +74,7 @@ copy /Y %part1_dir%\%listing_0041% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0041%_disassembled.asm
fc /B %part1_build_dir_listing_0041% %part1_build_dir_listing_0041%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0042=listing_0042_completionist_decode
set part1_build_dir_listing_0042=%part1_build_dir%\%listing_0042%
@@ -80,7 +84,7 @@ copy /Y %part1_dir%\%listing_0042% %part1_build_dir% 1>NUL
nasm %part1_build_dir_listing_0042%_disassembled.asm
fc /B %part1_build_dir_listing_0042% %part1_build_dir_listing_0042%_disassembled || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0043=listing_0043_immediate_movs
set part1_build_dir_listing_0043=%part1_build_dir%\%listing_0043%
@@ -95,7 +99,7 @@ nasm %part1_build_dir_listing_0043%_disassembled.asm
fc /B %part1_build_dir_listing_0043% %part1_build_dir_listing_0043%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0043%.txt %part1_build_dir_listing_0043%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0044=listing_0044_register_movs
set part1_build_dir_listing_0044=%part1_build_dir%\%listing_0044%
@@ -110,7 +114,7 @@ nasm %part1_build_dir_listing_0044%_disassembled.asm
fc /B %part1_build_dir_listing_0044% %part1_build_dir_listing_0044%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0044%.txt %part1_build_dir_listing_0044%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0045=listing_0045_challenge_register_movs
set part1_build_dir_listing_0045=%part1_build_dir%\%listing_0045%
@@ -125,7 +129,7 @@ nasm %part1_build_dir_listing_0045%_disassembled.asm
fc /B %part1_build_dir_listing_0045% %part1_build_dir_listing_0045%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0045%.txt %part1_build_dir_listing_0045%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0046=listing_0046_add_sub_cmp
set part1_build_dir_listing_0046=%part1_build_dir%\%listing_0046%
@@ -140,7 +144,7 @@ nasm %part1_build_dir_listing_0046%_disassembled.asm
fc /B %part1_build_dir_listing_0046% %part1_build_dir_listing_0046%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0046%.txt %part1_build_dir_listing_0046%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0047=listing_0047_challenge_flags
set part1_build_dir_listing_0047=%part1_build_dir%\%listing_0047%
@@ -155,7 +159,7 @@ nasm %part1_build_dir_listing_0047%_disassembled.asm
fc /B %part1_build_dir_listing_0047% %part1_build_dir_listing_0047%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0047%.txt %part1_build_dir_listing_0047%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0048=listing_0048_ip_register
set part1_build_dir_listing_0048=%part1_build_dir%\%listing_0048%
@@ -170,7 +174,7 @@ nasm %part1_build_dir_listing_0048%_disassembled.asm
fc /B %part1_build_dir_listing_0048% %part1_build_dir_listing_0048%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0048%.txt %part1_build_dir_listing_0048%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0049=listing_0049_conditional_jumps
set part1_build_dir_listing_0049=%part1_build_dir%\%listing_0049%
@@ -185,7 +189,7 @@ nasm %part1_build_dir_listing_0049%_disassembled.asm
fc /B %part1_build_dir_listing_0049% %part1_build_dir_listing_0049%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0049%.txt %part1_build_dir_listing_0049%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0050=listing_0050_challenge_jumps
set part1_build_dir_listing_0050=%part1_build_dir%\%listing_0050%
@@ -200,7 +204,7 @@ nasm %part1_build_dir_listing_0050%_disassembled.asm
fc /B %part1_build_dir_listing_0050% %part1_build_dir_listing_0050%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0050%.txt %part1_build_dir_listing_0050%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0051=listing_0051_memory_mov
set part1_build_dir_listing_0051=%part1_build_dir%\%listing_0051%
@@ -215,7 +219,7 @@ nasm %part1_build_dir_listing_0051%_disassembled.asm
fc /B %part1_build_dir_listing_0051% %part1_build_dir_listing_0051%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0051%.txt %part1_build_dir_listing_0051%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0052=listing_0052_memory_add_loop
set part1_build_dir_listing_0052=%part1_build_dir%\%listing_0052%
@@ -230,7 +234,7 @@ nasm %part1_build_dir_listing_0052%_disassembled.asm
fc /B %part1_build_dir_listing_0052% %part1_build_dir_listing_0052%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0052%.txt %part1_build_dir_listing_0052%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0053=listing_0053_add_loop_challenge
set part1_build_dir_listing_0053=%part1_build_dir%\%listing_0053%
@@ -245,7 +249,7 @@ nasm %part1_build_dir_listing_0053%_disassembled.asm
fc /B %part1_build_dir_listing_0053% %part1_build_dir_listing_0053%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0053%.txt %part1_build_dir_listing_0053%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0054=listing_0054_draw_rectangle
set part1_build_dir_listing_0054=%part1_build_dir%\%listing_0054%
@@ -262,7 +266,7 @@ nasm %part1_build_dir_listing_0054%_disassembled.asm
fc /B %part1_build_dir_listing_0054% %part1_build_dir_listing_0054%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0054%.txt %part1_build_dir_listing_0054%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0055=listing_0055_challenge_rectangle
set part1_build_dir_listing_0055=%part1_build_dir%\%listing_0055%
@@ -279,7 +283,7 @@ nasm %part1_build_dir_listing_0055%_disassembled.asm
fc /B %part1_build_dir_listing_0055% %part1_build_dir_listing_0055%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0055%.txt %part1_build_dir_listing_0055%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0056=listing_0056_estimating_cycles
set part1_build_dir_listing_0056=%part1_build_dir%\%listing_0056%
@@ -297,7 +301,7 @@ nasm %part1_build_dir_listing_0056%_disassembled.asm
fc /B %part1_build_dir_listing_0056% %part1_build_dir_listing_0056%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0056%.txt %part1_build_dir_listing_0056%_disassembled.txt || exit /b 1
REM ================================================================================================
REM ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0057=listing_0057_challenge_cycles
set part1_build_dir_listing_0057=%part1_build_dir%\%listing_0057%
@@ -315,8 +319,8 @@ nasm %part1_build_dir_listing_0057%_disassembled.asm
fc /B %part1_build_dir_listing_0057% %part1_build_dir_listing_0057%_disassembled || exit /b 1
fc /N %part1_build_dir_listing_0057%.txt %part1_build_dir_listing_0057%_disassembled.txt || exit /b 1
REM Part 2 =========================================================================================
REM Build ==========================================================================================
REM Part 2 /////////////////////////////////////////////////////////////////////////////////////////
REM Build //////////////////////////////////////////////////////////////////////////////////////////
:part2
pushd %part2_build_dir%
cl %part2_dir%\haversine_generator.c /W4 /WX /Z7 /nologo /Fe:haversine_generator_debug || exit /b 1
@@ -335,3 +339,12 @@ cl %part2_dir%\listing_0071_os_timer_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listin
cl %part2_dir%\listing_0072_cpu_timer_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0072_cpu_timer_main_release || exit /b 1
cl %part2_dir%\listing_0073_cpu_timer_guessfreq_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0073_cpu_timer_guessfreq_release || exit /b 1
popd
REM Part 3 /////////////////////////////////////////////////////////////////////////////////////////
REM Build //////////////////////////////////////////////////////////////////////////////////////////
:part3
pushd %part3_build_dir%
cl %part3_dir%\repetition_tester.c /W4 /WX /Z7 /nologo /Fe:repetition_tester_debug || exit /b 1
cl %part3_dir%\repetition_tester.c /W4 /WX /Z7 /nologo /O2 /Fe:repetition_tester_release || exit /b 1
popd
+12 -22
View File
@@ -382,8 +382,7 @@ S86_Opcode S86_DecodeOpcode(S86_BufferIterator *buffer_it,
size_t op_code_size = 0;
op_code_bytes[op_code_size++] = S86_BufferIteratorNextByte(buffer_it);
// NOTE: Match the assembly bytes to the desired instruction
// =====================================================================
// NOTE: Match the assembly bytes to the desired instruction ///////////////////////////////////
S86_OpDecodeType op_decode_type = S86_OpDecodeType_Count;
S86_OpDecode const *op_decode = NULL;
for (size_t op_index = 0;
@@ -392,13 +391,11 @@ S86_Opcode S86_DecodeOpcode(S86_BufferIterator *buffer_it,
{
S86_OpDecode const *item = decode_table + op_index;
// NOTE: Check first instruction byte
// =================================================================
// NOTE: Check first instruction byte //////////////////////////////////////////////////////
if ((op_code_bytes[0] & item->op_mask0) != item->op_bits0)
continue;
// NOTE Check multi-byte instruction
// =================================================================
// NOTE Check multi-byte instruction ///////////////////////////////////////////////////////
// If the matched instruction has a bit mask for the 2nd byte, this
// is a multi-byte instruction. Check if the 2nd byte checks out.
bool op_match = true;
@@ -418,8 +415,7 @@ S86_Opcode S86_DecodeOpcode(S86_BufferIterator *buffer_it,
}
}
// NOTE: Disassemble bytes to assembly mnemonics
// =================================================================
// NOTE: Disassemble bytes to assembly mnemonics ///////////////////////////////////////////////
S86_ASSERT(op_code_size > 0 && op_code_size <= S86_ARRAY_UCOUNT(op_code_bytes));
S86_ASSERT(op_decode_type != S86_OpDecodeType_Count && "Unknown instruction");
@@ -677,8 +673,7 @@ S86_Opcode S86_DecodeOpcode(S86_BufferIterator *buffer_it,
case S86_OpDecodeType_ORImmediateToAccum: /*FALLTHRU*/
case S86_OpDecodeType_XORImmediateToAccum: /*FALLTHRU*/
case S86_OpDecodeType_MOVImmediateToReg: {
// NOTE: Parse opcode control bits
// =============================================================
// NOTE: Parse opcode control bits /////////////////////////////////////////////////////
S86_ASSERT(op_code_size == 1);
uint8_t w = 0;
if (op_decode_type == S86_OpDecodeType_ADDImmediateToAccum ||
@@ -695,16 +690,14 @@ S86_Opcode S86_DecodeOpcode(S86_BufferIterator *buffer_it,
w = (op_code_bytes[0] & 0b0000'1000) >> 3;
}
// NOTE: Parse data payload
// =============================================================
// NOTE: Parse data payload ////////////////////////////////////////////////////////////
uint16_t data = S86_BufferIteratorNextByte(buffer_it);
if (w) { // 16 bit data
uint8_t data_hi = S86_BufferIteratorNextByte(buffer_it);
data |= (uint16_t)(data_hi) << 8;
}
// NOTE: Disassemble
// =============================================================
// NOTE: Disassemble ///////////////////////////////////////////////////////////////////
result.effective_addr = S86_EffectiveAddress_Dest;
result.src = S86_MnemonicOp_Immediate;
result.wide = w;
@@ -893,8 +886,7 @@ char const CLI_ARG_DUMP[] = "--dump";
int main(int argc, char **argv)
{
// NOTE: Argument handling
// =========================================================================
// NOTE: Argument handling /////////////////////////////////////////////////////////////////////
if (argc < 2) {
PRINT_USAGE;
return -1;
@@ -965,8 +957,7 @@ int main(int argc, char **argv)
return -1;
}
// NOTE: Sim8086
// =========================================================================
// NOTE: Sim8086 ///////////////////////////////////////////////////////////////////////////////
S86_OpDecode const DECODE_TABLE[] = {
[S86_OpDecodeType_MOVRegOrMemToOrFromReg] = {.op_mask0 = 0b1111'1100, .op_mask1 = 0b0000'0000,
.op_bits0 = 0b1000'1000, .op_bits1 = 0b0000'0000, .mnemonic = S86_Mnemonic_MOV},
@@ -1257,8 +1248,7 @@ int main(int argc, char **argv)
.op_bits0 = 0b0010'0110, .op_bits1 = 0b0000'0000, .mnemonic = S86_Mnemonic_SEGMENT},
};
// NOTE: Decode assembly
// =========================================================================
// NOTE: Decode assembly ///////////////////////////////////////////////////////////////////////
if (exec_mode) {
if (log_cycle_counts != CycleCount_None) { // NOTE: Print disclaimer + header
S86_PrintLn(S86_STR8("**************"));
@@ -1337,7 +1327,7 @@ int main(int argc, char **argv)
continue;
}
// NOTE: Simulate instruction ==============================================================
// NOTE: Simulate instruction //////////////////////////////////////////////////////////////
bool cycle_count_8088 = log_cycle_counts == CycleCount_8088;
uint32_t base_clocks = 0;
uint32_t effective_address_clocks = 0;
@@ -1800,7 +1790,7 @@ int main(int argc, char **argv)
clocks_counter += base_clocks + effective_address_clocks + transfer_penalty_clocks;
// NOTE: Printing ==========================================================================
// NOTE: Printing //////////////////////////////////////////////////////////////////////////
S86_PrintFmt(" ; ");
// NOTE: Clocks
+1 -2
View File
@@ -1,5 +1,4 @@
// NOTE: Sim8086
// ============================================================================
// NOTE: Sim8086 ///////////////////////////////////////////////////////////////////////////////////
typedef enum S86_OpDecodeType {
S86_OpDecodeType_MOVRegOrMemToOrFromReg,
S86_OpDecodeType_MOVImmediateToRegOrMem,
+3 -6
View File
@@ -13,8 +13,7 @@
#define PRINT_USAGE HAV_PrintLnFmt("Usage: %s [uniform/cluster] [random seed] [number of coordinate pairs to generate]", argv[0])
int main(int argc, char **argv)
{
// NOTE: Unit Tests
// =========================================================================
// NOTE: Unit Tests ////////////////////////////////////////////////////////////////////////////
{
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("00"));
@@ -44,8 +43,7 @@ int main(int argc, char **argv)
}
}
// NOTE: Arg Parsing
// =========================================================================
// NOTE: Arg Parsing ///////////////////////////////////////////////////////////////////////////
if (argc != 4) {
PRINT_USAGE;
return -1;
@@ -95,8 +93,7 @@ int main(int argc, char **argv)
return -1;
}
// NOTE: Generator
// =========================================================================
// NOTE: Generator /////////////////////////////////////////////////////////////////////////////
uint64_t point_count = number_of_coordinate_pairs_to_generate_u64_result.value;
uint64_t random_seed = random_seed_u64_result.value;
uint64_t rng_state = random_seed;
+8 -16
View File
@@ -1,5 +1,4 @@
// NOTE: Implementation
// ============================================================================
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
bool HAV_Str8_Equals(HAV_Str8 lhs, HAV_Str8 rhs)
{
bool result = lhs.size == rhs.size && memcmp(lhs.data, rhs.data, lhs.size) == 0;
@@ -181,14 +180,12 @@ HAV_Buffer HAV_FileRead(char const *file_path)
{
HAV_Buffer result = {0};
// NOTE: Determine file size
// =========================================================================
// NOTE: Determine file size ///////////////////////////////////////////////////////////////////
WIN32_FILE_ATTRIBUTE_DATA file_attrib_data = {0};
if (GetFileAttributesEx(file_path, GetFileExInfoStandard, &file_attrib_data) == 0)
return result;
// NOTE: Open file
// =========================================================================
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_READ,
@@ -202,8 +199,7 @@ HAV_Buffer HAV_FileRead(char const *file_path)
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Allocate buffer
// =========================================================================
// NOTE: Allocate buffer ///////////////////////////////////////////////////////////////////////
uint64_t file_size = (uint64_t)file_attrib_data.nFileSizeHigh << 32 | (uint64_t)file_attrib_data.nFileSizeLow << 0;
HAV_ASSERT(file_size < (DWORD)-1);
char *buffer = VirtualAlloc(
@@ -216,8 +212,7 @@ HAV_Buffer HAV_FileRead(char const *file_path)
if (!buffer)
goto end;
// NOTE: Read file to buffer
// =========================================================================
// NOTE: Read file to buffer ///////////////////////////////////////////////////////////////////
DWORD bytes_read = 0;
HAV_ProfilerZone prof_file_read_zone = HAV_Profiler_BeginZoneBandwidth("File Read", file_size);
BOOL read_file_result = ReadFile(
@@ -229,8 +224,7 @@ HAV_Buffer HAV_FileRead(char const *file_path)
);
HAV_Profiler_EndZone(prof_file_read_zone);
// NOTE: Handle read result
// =========================================================================
// NOTE: Handle read result ////////////////////////////////////////////////////////////////////
if (read_file_result == 0) {
VirtualFree(buffer, 0, MEM_RELEASE);
} else {
@@ -253,8 +247,7 @@ bool HAV_FileWrite(char const *file_path, void const *buffer, size_t buffer_size
{
bool result = false;
// NOTE: Open file
// =========================================================================
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
@@ -268,8 +261,7 @@ bool HAV_FileWrite(char const *file_path, void const *buffer, size_t buffer_size
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Write file to disk
// =========================================================================
// NOTE: Write file to disk ////////////////////////////////////////////////////////////////////
DWORD bytes_written = 0;
BOOL write_file_result = WriteFile(
/*HANDLE hFile*/ file_handle,
+10 -18
View File
@@ -1,7 +1,6 @@
#include <stdint.h>
// NOTE: Macros
// ============================================================================
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
#define HAV_STRINGIFY2(token) #token
#define HAV_STRINGIFY(token) HAV_STRINGIFY2(token)
@@ -24,8 +23,7 @@ typedef float f32;
typedef double f64;
typedef uint64_t u64;
// NOTE: Globals
// ============================================================================
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Globals {
HANDLE stdout_handle;
bool write_to_console;
@@ -33,8 +31,7 @@ typedef struct HAV_Globals {
HAV_Globals pap_globals;
// NOTE: Strings
// ============================================================================
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Str8 {
char *data;
size_t size;
@@ -60,8 +57,7 @@ HAV_Str8BinarySplitResult HAV_Str8_BinarySplit(HAV_Str8 buffer, HAV_Str8 find);
bool HAV_CharIsWhiteSpace(char ch);
bool HAV_CharIsDigit(char ch);
// NOTE: Profiler
// ============================================================================
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_ProfilerAnchor {
HAV_Str8 label;
u64 elapsed_tsc_exclusive; // Does not include children
@@ -91,12 +87,11 @@ static HAV_Profiler g_profiler;
#define HAV_Profiler_BeginZone(label) HAV_Profiler_BeginZone_(HAV_STR8(label), __COUNTER__ + 1, 0)
#define HAV_Profiler_BeginZoneBandwidth(label, byte_count) HAV_Profiler_BeginZone_(HAV_STR8(label), __COUNTER__ + 1, byte_count)
static void HAV_Profiler_Dump();
static void HAV_Profiler_Dump ();
static HAV_ProfilerZone HAV_Profiler_BeginZone_(HAV_Str8 label, uint32_t index, u64 byte_count);
static void HAV_Profiler_EndZone(HAV_ProfilerZone zone);
static void HAV_Profiler_EndZone (HAV_ProfilerZone zone);
// NOTE: PCG32
// ============================================================================
// NOTE: PCG32 /////////////////////////////////////////////////////////////////////////////////////
// NOTE: PCG RNG from Demetri Spanos: https://github.com/demetri/scribbles
// pcg32_pie, based on the minimal C version from O'Neill at pcg-random.org;
// I've made a few (subjective) UX improvements for beginner use
@@ -119,8 +114,7 @@ static void HAV_Profiler_EndZone(HAV_ProfilerZone zone);
uint32_t HAV_PCG32_Pie (uint64_t *state);
f64 HAV_PCG32_PieF64(uint64_t *state, f64 min, f64 max);
// NOTE: Buffer
// ============================================================================
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Buffer {
char *data;
size_t size;
@@ -136,14 +130,12 @@ HAV_BufferIterator HAV_BufferIteratorInit (HAV_Buffer buffer);
bool HAV_BufferIteratorHasMoreBytes(HAV_BufferIterator it);
uint8_t HAV_BufferIteratorNextByte (HAV_BufferIterator *it);
// NOTE: File
// ============================================================================
// NOTE: File //////////////////////////////////////////////////////////////////////////////////////
HAV_Buffer HAV_FileRead (char const *file_path);
void HAV_FileFree (HAV_Buffer buffer);
bool HAV_FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
// NOTE: Print
// ============================================================================
// NOTE: Print /////////////////////////////////////////////////////////////////////////////////////
void HAV_PrintHandle(void *handle, HAV_Str8 string);
void HAV_PrintLn (HAV_Str8 string);
void HAV_PrintLnFmt (char const *fmt, ...);
@@ -0,0 +1,537 @@
/* ========================================================================
(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 89
======================================================================== */
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
return Result;
}
static void Error(json_parser *Parser, json_token Token, char const *Message)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
while(Element)
{
json_element *FreeElement = Element;
Element = Element->NextSibling;
FreeJSON(FreeElement->FirstSubElement);
free(FreeElement);
}
}
static json_element *LookupElement(json_element *Object, buffer ElementName)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
TimeFunction;
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)
{
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"));
}
}
FreeJSON(JSON);
return PairCount;
}
@@ -0,0 +1,184 @@
/* ========================================================================
(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 90
======================================================================== */
/* 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;
};
#include "listing_0087_simplified_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_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)
{
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)
{
TimeFunction;
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;
}
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
+142
View File
@@ -0,0 +1,142 @@
/* ========================================================================
(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 91
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
GlobalProfilerParent = AnchorIndex;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - 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 TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, 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(")\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
#else
#define TimeBlock(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeFunction TimeBlock(__func__)
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = ReadCPUTimer();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = ReadCPUTimer();
u64 CPUFreq = EstimateCPUTimerFreq();
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(CPUFreq)
{
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
}
PrintAnchorData(TotalCPUElapsed);
}
@@ -0,0 +1,185 @@
/* ========================================================================
(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 92
======================================================================== */
/* 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_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_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)
{
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)
{
TimeFunction;
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;
@@ -0,0 +1,184 @@
/* ========================================================================
(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 93
======================================================================== */
/* 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;
};
#include "listing_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_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)
{
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)
{
TimeFunction;
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;
@@ -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;
}
@@ -0,0 +1,185 @@
/* ========================================================================
(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 95
======================================================================== */
/* 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_0091_switchable_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)
{
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)
{
TimeFunction;
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;
+176
View File
@@ -0,0 +1,176 @@
/* ========================================================================
(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 96
======================================================================== */
#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;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
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 TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, 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(")\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
#else
#define TimeBlock(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#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);
}
+185
View File
@@ -0,0 +1,185 @@
/* ========================================================================
(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 97
======================================================================== */
/* 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_0096_selectable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0083_recursive_timed_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)
{
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)
{
TimeFunction;
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;
+186
View File
@@ -0,0 +1,186 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 98
======================================================================== */
/* 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
#define READ_BLOCK_TIMER ReadOSTimer
#include "listing_0096_selectable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0083_recursive_timed_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)
{
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)
{
TimeFunction;
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;
@@ -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 99
======================================================================== */
/* 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
#define READ_BLOCK_TIMER ReadOSTimer
#include "listing_0091_switchable_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)
{
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)
{
TimeFunction;
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;
+353
View File
@@ -0,0 +1,353 @@
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
bool Str8_Equals(Str8 lhs, Str8 rhs)
{
bool result = lhs.size == rhs.size && memcmp(lhs.data, rhs.data, lhs.size) == 0;
return result;
}
Str8ToU64Result Str8_ToU64(Str8 string)
{
Str8ToU64Result result = {0};
size_t ch_index = 0;
while (ch_index < string.size && CharIsWhiteSpace(string.data[ch_index]))
ch_index++;
for (; ch_index < string.size; ch_index++) {
char ch = string.data[ch_index];
if (ch >= '0' && ch <= '9') {
result.value = (result.value * 10) + (ch - '0');
} else {
return result;
}
}
result.success = true;
return result;
}
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find)
{
Str8BinarySplitResult result = {0};
result.lhs = buffer;
for (size_t index = 0; (index + find.size) <= buffer.size; index++) {
Str8 check = {buffer.data + index, find.size};
if (Str8_Equals(find, check)) {
result.lhs.size = index;
result.rhs.data = check.data + find.size;
result.rhs.size = buffer.size - (index + find.size);
break;
}
}
return result;
}
bool CharIsWhiteSpace(char ch)
{
bool result = ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
return result;
}
bool CharIsDigit(char ch)
{
bool result = ch >= '0' && ch <= '9';
return result;
}
void Profiler_Dump()
{
u64 total_elapsed_tsc = g_profiler.end_tsc - g_profiler.begin_tsc;
u64 cpu_frequency = EstimateCPUTimerFreq();
if (cpu_frequency)
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)total_elapsed_tsc / (f64)cpu_frequency, cpu_frequency);
for (uint32_t index = 1; index < ARRAY_UCOUNT(g_profiler.anchors); index++) {
ProfilerAnchor const *anchor = g_profiler.anchors + index;
if (!anchor->elapsed_tsc_inclusive)
break;
f64 percent = total_elapsed_tsc ? (f64)anchor->elapsed_tsc_exclusive / (f64)total_elapsed_tsc * 100.0 : 100.0;
printf(" %.*s[%zu]: %llu (%.2f%%", STR8_FMT(anchor->label), anchor->hits, anchor->elapsed_tsc_exclusive, percent);
if (anchor->elapsed_tsc_inclusive != anchor->elapsed_tsc_exclusive) {
f64 percent_w_children = total_elapsed_tsc ? ((f64)anchor->elapsed_tsc_inclusive / (f64)total_elapsed_tsc * 100.0) : 100.0;
printf(", %.2f%% w/children", percent_w_children);
}
printf(")");
if (anchor->byte_count) {
f64 megabytes_processed = anchor->byte_count / (1024.f * 1024.f);
f64 elapsed_s = anchor->elapsed_tsc_inclusive / CAST(f64)cpu_frequency;
f64 bytes_per_s = anchor->byte_count / elapsed_s;
f64 gigabytes_bandwidth = bytes_per_s / (1024.f * 1024.f * 1024.f);
printf(" %.3fmb at %.2fgb/s", megabytes_processed, gigabytes_bandwidth);
}
printf("\n");
}
}
ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count)
{
ProfilerZone result = {0};
#if defined(PROFILER)
result.index = index;
result.label = label;
result.tsc = ReadCPUTimer();
result.elapsed_tsc_inclusive = g_profiler.anchors[index].elapsed_tsc_inclusive;
result.byte_count = byte_count;
result.parent_index = g_profiler.parent_index;
g_profiler.parent_index = index;
#else
(void)label; (void)index; (void)byte_count;
#endif
return result;
}
void Profiler_EndZone(ProfilerZone zone)
{
#if defined(PROFILER)
u64 elapsed_tsc = ReadCPUTimer() - zone.tsc;
ProfilerAnchor* anchor = g_profiler.anchors + zone.index;
ProfilerAnchor* parent = g_profiler.anchors + zone.parent_index;
anchor->elapsed_tsc_exclusive += elapsed_tsc;
anchor->elapsed_tsc_inclusive = zone.elapsed_tsc_inclusive + elapsed_tsc;
anchor->label = zone.label;
anchor->byte_count += zone.byte_count;
anchor->hits++;
parent->elapsed_tsc_exclusive -= elapsed_tsc;
g_profiler.parent_index = zone.parent_index;
#else
(void)zone;
#endif
}
#pragma warning(push)
#pragma warning(disable: 4146) // warning C4146: unary minus operator applied to unsigned type, result still unsigned
uint32_t PCG32_Pie (uint64_t *state)
{
uint64_t old = *state ^ 0xc90fdaa2adf85459ULL;
*state = *state * 6364136223846793005ULL + 0xc90fdaa2adf85459ULL;
uint32_t xorshifted = (uint32_t)(((old >> 18u) ^ old) >> 27u);
uint32_t rot = old >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
#pragma warning(pop)
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max)
{
uint32_t u32_value = PCG32_Pie(state);
f64 t01 = CAST(f64)u32_value / CAST(f64)CAST(uint32_t)-1;
f64 result = min + (max - min) * t01;
return result;
}
bool BufferIsValid(Buffer buffer)
{
bool result = buffer.data && buffer.size;
return result;
}
BufferIterator BufferIteratorInit(Buffer buffer)
{
BufferIterator result = {0};
result.buffer = buffer;
return result;
}
bool BufferIteratorHasMoreBytes(BufferIterator it)
{
bool result = BufferIsValid(it.buffer) && it.index < it.buffer.size;
return result;
}
uint8_t BufferIteratorPeekByte(BufferIterator *it)
{
ASSERT(it);
ASSERT(BufferIsValid(it->buffer));
ASSERT(it->index < it->buffer.size);
uint8_t result = it->buffer.data[it->index];
return result;
}
uint8_t BufferIteratorNextByte(BufferIterator *it)
{
uint8_t result = BufferIteratorPeekByte(it);
it->index++;
return result;
}
Buffer FileRead(char const *file_path)
{
Buffer result = {0};
// NOTE: Determine file size ///////////////////////////////////////////////////////////////////
WIN32_FILE_ATTRIBUTE_DATA file_attrib_data = {0};
if (GetFileAttributesEx(file_path, GetFileExInfoStandard, &file_attrib_data) == 0)
return result;
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_READ,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ OPEN_EXISTING,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Allocate buffer ///////////////////////////////////////////////////////////////////////
uint64_t file_size = (uint64_t)file_attrib_data.nFileSizeHigh << 32 | (uint64_t)file_attrib_data.nFileSizeLow << 0;
ASSERT(file_size < (DWORD)-1);
char *buffer = VirtualAlloc(
/*LPVOID lpAddress*/ NULL,
/*SIZE_T dwSize*/ file_size,
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
/*DWORD flProtect*/ PAGE_READWRITE
);
if (!buffer)
goto end;
// NOTE: Read file to buffer ///////////////////////////////////////////////////////////////////
DWORD bytes_read = 0;
ProfilerZone prof_file_read_zone = Profiler_BeginZoneBandwidth("File Read", file_size);
BOOL read_file_result = ReadFile(
/*HANDLE hFile*/ file_handle,
/*LPVOID lpBuffer*/ buffer,
/*DWORD nNumberOfBytesToRead*/ CAST(DWORD)file_size,
/*LPDWORD lpNumberOfBytesRead*/ &bytes_read,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
Profiler_EndZone(prof_file_read_zone);
// NOTE: Handle read result ////////////////////////////////////////////////////////////////////
if (read_file_result == 0) {
VirtualFree(buffer, 0, MEM_RELEASE);
} else {
result.data = buffer;
result.size = file_size;
}
end:
CloseHandle(file_handle);
return result;
};
void FileFree(Buffer buffer)
{
if (BufferIsValid(buffer))
VirtualFree(buffer.data, 0, MEM_RELEASE);
}
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size)
{
bool result = false;
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Write file to disk ////////////////////////////////////////////////////////////////////
DWORD bytes_written = 0;
BOOL write_file_result = WriteFile(
/*HANDLE hFile*/ file_handle,
/*LPVOID lpBuffer*/ buffer,
/*DWORD nNumberOfBytesToWrite*/ CAST(DWORD)buffer_size,
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
ASSERT(bytes_written == buffer_size);
result = write_file_result && bytes_written == buffer_size;
CloseHandle(file_handle);
return result;
};
void PrintHandle(void *handle, Str8 string)
{
DWORD bytes_written = 0;
WriteFile(handle, string.data, CAST(DWORD)string.size, &bytes_written, NULL);
(void)bytes_written;
}
void Print(Str8 string)
{
if (pap_globals.stdout_handle == NULL) {
pap_globals.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
BOOL get_console_mode_result = GetConsoleMode(
/*HANDLE hConsoleHandle*/ pap_globals.stdout_handle,
/*LPDWORD lpMode*/ &mode
);
pap_globals.write_to_console = get_console_mode_result != 0;
}
ASSERT(string.size < CAST(DWORD)-1);
if (pap_globals.write_to_console) {
DWORD chars_written = 0;
WriteConsoleA(pap_globals.stdout_handle, string.data, (DWORD)string.size, &chars_written, NULL);
} else {
PrintHandle(pap_globals.stdout_handle, string);
}
}
void PrintFmt(char const *fmt, ...)
{
va_list args, args_copy;
va_start(args, fmt);
va_copy(args_copy, args);
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
char buffer[8192];
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
Str8 string = {.data = buffer, .size = string_size};
Print(string);
}
va_end(args);
}
void PrintLn(Str8 string)
{
Print(string);
Print(STR8("\n"));
}
void PrintLnFmt(char const *fmt, ...)
{
va_list args, args_copy;
va_start(args, fmt);
va_copy(args_copy, args);
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
char buffer[8192];
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
Str8 string = {.data = buffer, .size = string_size};
PrintLn(string);
}
va_end(args);
}
+145
View File
@@ -0,0 +1,145 @@
#include <stdint.h>
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
#define STRINGIFY2(token) #token
#define STRINGIFY(token) STRINGIFY2(token)
#if defined(NDEBUG)
#define ASSERT(expr)
#else
#define ASSERT(expr) \
if (!(expr)) { \
PrintLnFmt("Assertion triggered [file=\"" __FILE__ ":" STRINGIFY(__LINE__) "\", expr=\"" #expr "\"]"); \
__debugbreak(); \
}
#endif
#define ARRAY_UCOUNT(array) sizeof((array)) / sizeof((array)[0])
#define CAST(Type) (Type)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
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;
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
typedef struct Globals {
HANDLE stdout_handle;
bool write_to_console;
} Globals;
Globals pap_globals;
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
typedef struct Str8 {
char *data;
size_t size;
} Str8;
typedef struct Str8ToU64Result {
bool success;
uint64_t value;
} Str8ToU64Result;
typedef struct Str8BinarySplitResult {
Str8 lhs;
Str8 rhs;
} Str8BinarySplitResult;
#define STR8(string) (Str8){.data = (char *)(string), .size = ARRAY_UCOUNT(string) - 1 }
#define STR8_FMT(string) (int)((string).size), (string).data
bool Str8_Equals(Str8 lhs, Str8 rhs);
Str8ToU64Result Str8_ToU64(Str8 string);
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find);
bool CharIsWhiteSpace(char ch);
bool CharIsDigit(char ch);
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
typedef struct ProfilerAnchor {
Str8 label;
u64 elapsed_tsc_exclusive; // Does not include children
u64 elapsed_tsc_inclusive; // Includes children
u64 byte_count;
u64 hits;
} ProfilerAnchor;
typedef struct Profiler {
ProfilerAnchor anchors[4096];
u64 begin_tsc;
u64 end_tsc;
u64 parent_index;
} Profiler;
typedef struct ProfilerZone {
u64 parent_index;
uint32_t index;
Str8 label;
u64 elapsed_tsc_inclusive;
u64 tsc;
u64 byte_count;
} ProfilerZone;
static Profiler g_profiler;
#define Profiler_BeginZone(label) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, 0)
#define Profiler_BeginZoneBandwidth(label, byte_count) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, byte_count)
static void Profiler_Dump ();
static ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count);
static void Profiler_EndZone (ProfilerZone zone);
// NOTE: PCG32 /////////////////////////////////////////////////////////////////////////////////////
// NOTE: PCG RNG from Demetri Spanos: https://github.com/demetri/scribbles
// pcg32_pie, based on the minimal C version from O'Neill at pcg-random.org;
// I've made a few (subjective) UX improvements for beginner use
//
// I'm not allowing the user to pick the stream/increment constant at all,
// since there is almost never a reason for doing this in common applications.
// This means that the prng state is reduced to a single uint64_t which also
// means we can avoid having a state struct at all. The (fixed) stream constant
// uses the leading hex digits of pi and e multipled by 2^30 (c90fdaa2 and
// adf85459).
//
// I have also added an XOR with the same digits on the output path prior
// to xorshift mixing. This prevents the "surprising" result that the
// first "random 32-bit number" from a (very common) 0 seed is 0.
//
// use:
// uint64_t state = 12345; // whatever you like can go here
// uint32_t some_random_32_bits = pcg32_pie(&state);
// uint32_t more_random_32_bits = pcg32_pie(&state);
uint32_t PCG32_Pie (uint64_t *state);
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max);
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
typedef struct Buffer {
char *data;
size_t size;
} Buffer;
typedef struct BufferIterator {
Buffer buffer;
size_t index;
} BufferIterator;
bool BufferIsValid (Buffer buffer);
BufferIterator BufferIteratorInit (Buffer buffer);
bool BufferIteratorHasMoreBytes(BufferIterator it);
uint8_t BufferIteratorNextByte (BufferIterator *it);
// NOTE: File //////////////////////////////////////////////////////////////////////////////////////
Buffer FileRead (char const *file_path);
void FileFree (Buffer buffer);
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
// NOTE: Print /////////////////////////////////////////////////////////////////////////////////////
void PrintHandle(void *handle, Str8 string);
void PrintLn (Str8 string);
void PrintLnFmt (char const *fmt, ...);
+53
View File
@@ -0,0 +1,53 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 65
======================================================================== */
static f64 Square(f64 A)
{
f64 Result = (A*A);
return Result;
}
static f64 RadiansFromDegrees(f64 Degrees)
{
f64 Result = 0.01745329251994329577 * Degrees;
return Result;
}
// NOTE(casey): EarthRadius is generally expected to be 6372.8
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
{
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
question on which these homework exercises are loosely based.
*/
f64 lat1 = Y0;
f64 lat2 = Y1;
f64 lon1 = X0;
f64 lon2 = X1;
f64 dLat = RadiansFromDegrees(lat2 - lat1);
f64 dLon = RadiansFromDegrees(lon2 - lon1);
lat1 = RadiansFromDegrees(lat1);
lat2 = RadiansFromDegrees(lat2);
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
f64 c = 2.0*asin(sqrt(a));
f64 Result = EarthRadius * c;
return Result;
}
+72
View File
@@ -0,0 +1,72 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 68
======================================================================== */
struct buffer
{
size_t Count;
u8 *Data;
};
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
static b32 IsInBounds(buffer Source, u64 At)
{
b32 Result = (At < Source.Count);
return Result;
}
static b32 AreEqual(buffer A, buffer B)
{
if(A.Count != B.Count)
{
return false;
}
for(u64 Index = 0; Index < A.Count; ++Index)
{
if(A.Data[Index] != B.Data[Index])
{
return false;
}
}
return true;
}
static buffer AllocateBuffer(size_t Count)
{
buffer Result = {};
Result.Data = (u8 *)malloc(Count);
if(Result.Data)
{
Result.Count = Count;
}
else
{
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
}
return Result;
}
static void FreeBuffer(buffer *Buffer)
{
if(Buffer->Data)
{
free(Buffer->Data);
}
*Buffer = {};
}
+98
View File
@@ -0,0 +1,98 @@
/* ========================================================================
(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 74
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
#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;
}
@@ -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
View File
@@ -0,0 +1,194 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 100
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#ifndef READ_BLOCK_TIMER
#define READ_BLOCK_TIMER ReadCPUTimer
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
u64 ProcessedByteCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_, u64 ByteCount)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
Anchor->ProcessedByteCount += ByteCount;
GlobalProfilerParent = AnchorIndex;
StartTSC = READ_BLOCK_TIMER();
}
~profile_block(void)
{
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
Parent->TSCElapsedExclusive -= Elapsed;
Anchor->TSCElapsedExclusive += Elapsed;
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
++Anchor->HitCount;
/* NOTE(casey): This write happens every time solely because there is no
straightforward way in C++ to have the same ease-of-use. In a better programming
language, it would be simple to have the anchor points gathered and labeled at compile
time, and this repetative write would be eliminated. */
Anchor->Label = Label;
}
char const *Label;
u64 OldTSCElapsedInclusive;
u64 StartTSC;
u32 ParentIndex;
u32 AnchorIndex;
};
#define NameConcat2(A, B) A##B
#define NameConcat(A, B) NameConcat2(A, B)
#define TimeBandwidth(Name, ByteCount) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1, ByteCount)
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, u64 TimerFreq, profile_anchor *Anchor)
{
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")");
if(Anchor->ProcessedByteCount)
{
f64 Megabyte = 1024.0f*1024.0f;
f64 Gigabyte = Megabyte*1024.0f;
f64 Seconds = (f64)Anchor->TSCElapsedInclusive / (f64)TimerFreq;
f64 BytesPerSecond = (f64)Anchor->ProcessedByteCount / Seconds;
f64 Megabytes = (f64)Anchor->ProcessedByteCount / (f64)Megabyte;
f64 GigabytesPerSecond = BytesPerSecond / Gigabyte;
printf(" %.3fmb at %.2fgb/s", Megabytes, GigabytesPerSecond);
}
printf("\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed, u64 TimerFreq)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, TimerFreq, Anchor);
}
}
}
#else
#define TimeBandwidth(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeBlock(Name) TimeBandwidth(Name, 0)
#define TimeFunction TimeBlock(__func__)
static u64 EstimateBlockTimerFreq(void)
{
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 BlockStart = READ_BLOCK_TIMER();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 BlockEnd = READ_BLOCK_TIMER();
u64 BlockElapsed = BlockEnd - BlockStart;
u64 BlockFreq = 0;
if(OSElapsed)
{
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
}
return BlockFreq;
}
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
u64 TimerFreq = EstimateBlockTimerFreq();
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(TimerFreq)
{
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
}
PrintAnchorData(TotalTSCElapsed, TimerFreq);
}
+186
View File
@@ -0,0 +1,186 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 101
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0100_bandwidth_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0094_profiled_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
TimeBandwidth("fread", Result.Count);
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeBandwidth(__func__, PairCount*sizeof(haversine_pair));
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
+151
View File
@@ -0,0 +1,151 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 102
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
struct read_parameters
{
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}
+211
View File
@@ -0,0 +1,211 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 103
======================================================================== */
enum test_mode : u32
{
TestMode_Uninitialized,
TestMode_Testing,
TestMode_Completed,
TestMode_Error,
};
struct repetition_test_results
{
u64 TestCount;
u64 TotalTime;
u64 MaxTime;
u64 MinTime;
};
struct repetition_tester
{
u64 TargetProcessedByteCount;
u64 CPUTimerFreq;
u64 TryForTime;
u64 TestsStartedAt;
test_mode Mode;
b32 PrintNewMinimums;
u32 OpenBlockCount;
u32 CloseBlockCount;
u64 TimeAccumulatedOnThisTest;
u64 BytesAccumulatedOnThisTest;
repetition_test_results Results;
};
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
{
f64 Result = 0.0;
if(CPUTimerFreq)
{
Result = (CPUTime / (f64)CPUTimerFreq);
}
return Result;
}
static void PrintTime(char const *Label, f64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
printf("%s: %.0f", Label, CPUTime);
if(CPUTimerFreq)
{
f64 Seconds = SecondsFromCPUTime(CPUTime, CPUTimerFreq);
printf(" (%fms)", 1000.0f*Seconds);
if(ByteCount)
{
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 BestBandwidth = ByteCount / (Gigabyte * Seconds);
printf(" %fgb/s", BestBandwidth);
}
}
}
static void PrintTime(char const *Label, u64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime(Label, (f64)CPUTime, CPUTimerFreq, ByteCount);
}
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime("Min", (f64)Results.MinTime, CPUTimerFreq, ByteCount);
printf("\n");
PrintTime("Max", (f64)Results.MaxTime, CPUTimerFreq, ByteCount);
printf("\n");
if(Results.TestCount)
{
PrintTime("Avg", (f64)Results.TotalTime / (f64)Results.TestCount, CPUTimerFreq, ByteCount);
printf("\n");
}
}
static void Error(repetition_tester *Tester, char const *Message)
{
Tester->Mode = TestMode_Error;
fprintf(stderr, "ERROR: %s\n", Message);
}
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
{
if(Tester->Mode == TestMode_Uninitialized)
{
Tester->Mode = TestMode_Testing;
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
Tester->CPUTimerFreq = CPUTimerFreq;
Tester->PrintNewMinimums = true;
Tester->Results.MinTime = (u64)-1;
}
else if(Tester->Mode == TestMode_Completed)
{
Tester->Mode = TestMode_Testing;
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
{
Error(Tester, "TargetProcessedByteCount changed");
}
if(Tester->CPUTimerFreq != CPUTimerFreq)
{
Error(Tester, "CPU frequency changed");
}
}
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
Tester->TestsStartedAt = ReadCPUTimer();
}
static void BeginTime(repetition_tester *Tester)
{
++Tester->OpenBlockCount;
Tester->TimeAccumulatedOnThisTest -= ReadCPUTimer();
}
static void EndTime(repetition_tester *Tester)
{
++Tester->CloseBlockCount;
Tester->TimeAccumulatedOnThisTest += ReadCPUTimer();
}
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
{
Tester->BytesAccumulatedOnThisTest += ByteCount;
}
static b32 IsTesting(repetition_tester *Tester)
{
if(Tester->Mode == TestMode_Testing)
{
u64 CurrentTime = ReadCPUTimer();
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
{
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
{
Error(Tester, "Unbalanced BeginTime/EndTime");
}
if(Tester->BytesAccumulatedOnThisTest != Tester->TargetProcessedByteCount)
{
Error(Tester, "Processed byte count mismatch");
}
if(Tester->Mode == TestMode_Testing)
{
repetition_test_results *Results = &Tester->Results;
u64 ElapsedTime = Tester->TimeAccumulatedOnThisTest;
Results->TestCount += 1;
Results->TotalTime += ElapsedTime;
if(Results->MaxTime < ElapsedTime)
{
Results->MaxTime = ElapsedTime;
}
if(Results->MinTime > ElapsedTime)
{
Results->MinTime = ElapsedTime;
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
Tester->TestsStartedAt = CurrentTime;
if(Tester->PrintNewMinimums)
{
PrintTime("Min", Results->MinTime, Tester->CPUTimerFreq, Tester->BytesAccumulatedOnThisTest);
printf(" \r");
}
}
Tester->OpenBlockCount = 0;
Tester->CloseBlockCount = 0;
Tester->TimeAccumulatedOnThisTest = 0;
Tester->BytesAccumulatedOnThisTest = 0;
}
}
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
{
Tester->Mode = TestMode_Completed;
printf(" \r");
PrintResults(Tester->Results, Tester->CPUTimerFreq, Tester->TargetProcessedByteCount);
}
}
b32 Result = (Tester->Mode == TestMode_Testing);
return Result;
}
+116
View File
@@ -0,0 +1,116 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 104
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,116 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 105
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"ReadFile", ReadViaReadFile},
{"_read", ReadViaRead},
{"fread", ReadViaFRead},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,219 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 106
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
enum allocation_type
{
AllocType_none,
AllocType_malloc,
AllocType_Count,
};
struct read_parameters
{
allocation_type AllocType;
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static char const *DescribeAllocationType(allocation_type AllocType)
{
char const *Result;
switch(AllocType)
{
case AllocType_none: {Result = "";} break;
case AllocType_malloc: {Result = "malloc";} break;
default : {Result = "UNKNOWN";} break;
}
return Result;
}
static void HandleAllocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
*Buffer = AllocateBuffer(Params->Dest.Count);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void HandleDeallocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
FreeBuffer(Buffer);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
HandleDeallocation(Params, &DestBuffer);
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}
@@ -0,0 +1,124 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 107
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
+145
View File
@@ -0,0 +1,145 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 108
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
#include <psapi.h>
typedef struct os_metrics
{
b32 Initialized;
HANDLE ProcessHandle;
} os_metrics;
static os_metrics GlobalMetrics;
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
static u64 ReadOSPageFaultCount(void)
{
PROCESS_MEMORY_COUNTERS_EX MemoryCounters = {};
MemoryCounters.cb = sizeof(MemoryCounters);
GetProcessMemoryInfo(GlobalMetrics.ProcessHandle, (PROCESS_MEMORY_COUNTERS *)&MemoryCounters, sizeof(MemoryCounters));
u64 Result = MemoryCounters.PageFaultCount;
return Result;
}
static void InitializeOSMetrics(void)
{
if(!GlobalMetrics.Initialized)
{
GlobalMetrics.Initialized = true;
GlobalMetrics.ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
}
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
static u64 ReadOSPageFaultCount(void)
{
// NOTE(casey): The course materials are not tested on MacOS/Linux.
// This code was contributed to the public github. It may or may not work
// for your system.
struct rusage Usage = {};
getrusage(RUSAGE_SELF, &Usage);
// ru_minflt the number of page faults serviced without any I/O activity.
// ru_majflt the number of page faults serviced that required I/O activity.
u64 Result = Usage.ru_minflt + Usage.ru_majflt;
return Result;
}
static void InitializeOSMetrics(void)
{
}
#endif
/* NOTE(casey): This does not need to be "inline", it could just be "static"
because compilers will inline it anyway. But compilers will warn about
static functions that aren't used. So "inline" is just the simplest way
to tell them to stop complaining about that. */
inline u64 ReadCPUTimer(void)
{
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
// with one of their performance counter read instructions, depending
// on which ones are available on your platform.
return __rdtsc();
}
static u64 EstimateCPUTimerFreq(void)
{
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
u64 CPUFreq = 0;
if(OSElapsed)
{
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
}
return CPUFreq;
}
@@ -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;
}
@@ -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);
}
}
@@ -0,0 +1,127 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 111
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0108_platform_metrics.cpp"
#include "listing_0109_pagefault_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
#include "listing_0110_pagefault_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"WriteToAllBytes", WriteToAllBytes},
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
InitializeOSMetrics();
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,89 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 112
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,89 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 113
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[TotalSize - 1 - Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}
+385
View File
@@ -0,0 +1,385 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdbool.h>
#include <stdio.h>
#include <Windows.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#include "base.h"
#include "listing_0108_platform_metrics.cpp"
#include "base.c"
#include "repetition_tester.h"
// NOTE: Allocate //////////////////////////////////////////////////////////////////////////////////
Str8 AllocTypeStr8(AllocType type)
{
Str8 result = {};
switch (type) {
case AllocType_None: result = STR8("Pre-Allocated"); break;
case AllocType_VirtualAlloc: result = STR8("VirtualAlloc"); break;
case AllocType_Malloc: result = STR8("Malloc"); break;
case AllocType_Count: break;
}
return result;
}
void TestAlloc(ReadArgs *args, Buffer *buffer)
{
switch (args->alloc_type) {
case AllocType_None: break;
case AllocType_VirtualAlloc: {
buffer->data = VirtualAlloc(/*LPVOID lpAddress*/ NULL,
/*SIZE_T dwSize*/ buffer->size,
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
/*DWORD flProtect*/ PAGE_READWRITE);
} break;
case AllocType_Malloc: {
buffer->data = malloc(buffer->size);
} break;
case AllocType_Count: break;
}
}
void TestDealloc(ReadArgs *args, Buffer *buffer)
{
switch (args->alloc_type) {
case AllocType_None: break;
case AllocType_Malloc: free(buffer->data); break;
case AllocType_VirtualAlloc: VirtualFree(buffer->data, 0, MEM_RELEASE); break;
case AllocType_Count: break;
}
}
// NOTE: RepTester /////////////////////////////////////////////////////////////////////////////////
void RepTester_Error(RepTester *tester, Str8 msg)
{
tester->mode = RepTesterMode_Error;
fprintf(stderr, "ERROR: %.*s\n", STR8_FMT(msg));
}
void RepTester_BeginTime(RepTester *tester)
{
tester->open_block_count++;
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->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->accumulated_on_this_test.e[RepTesterValueType_ByteCount] += bytes_read;
}
static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_count)
{
printf("%.*s: %.0f", STR8_FMT(label), cpu_time);
if (cpu_timer_freq) {
f64 seconds = cpu_time / CAST(f64)cpu_timer_freq;
printf(" (%fms)", 1000.0f * seconds);
if (byte_count) {
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 best_bandwidth = byte_count / (gigabyte * seconds);
printf(" %fgb/s", best_bandwidth);
}
}
}
static void RepTester_PrintValue(Str8 label, RepTesterValue value, u64 cpu_timer_freq)
{
u64 test_count = value.e[RepTesterValueType_TestCount];
f64 divisor = test_count ? (f64)test_count : 1;
f64 avg_values[ARRAY_UCOUNT(value.e)];
for (u64 index = 0; index < ARRAY_UCOUNT(value.e); index++)
avg_values[index] = value.e[index] / divisor;
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)
{
if (tester->mode == RepTesterMode_Testing) {
u64 current_time = ReadCPUTimer();
if (tester->open_block_count) {
if (tester->open_block_count != tester->close_block_count)
RepTester_Error(tester, STR8("Unbalanced begin/end time"));
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;
accum.e[RepTesterValueType_TestCount] = 1;
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;
RepTester_PrintValue(STR8("Min"), results->min, tester->cpu_timer_freq);
printf(" \r");
}
tester->open_block_count = 0;
tester->close_block_count = 0;
tester->accumulated_on_this_test = (RepTesterValue){};
}
}
if ((current_time - tester->start_time) > tester->run_duration) {
tester->mode = RepTesterMode_Complete;
printf(" \r");
RepTester_PrintResult(tester->results, tester->cpu_timer_freq);
}
}
bool result = tester->mode == RepTesterMode_Testing;
return result;
}
// NOTE: Read testing functions ////////////////////////////////////////////////////////////////////
static void ReadWithFRead(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
FILE *file = fopen(args->file_name.data, "rb");
if (file) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
RepTester_BeginTime(tester);
size_t result = fread(buffer.data, buffer.size, 1, file);
RepTester_EndTime(tester);
if (result == 1) {
RepTester_CountBytes(tester, buffer.size);
} else {
RepTester_Error(tester, STR8("fopen failed"));
}
TestDealloc(args, &buffer);
fclose(file);
} else {
RepTester_Error(tester, STR8("fopen failed"));
}
}
}
static void ReadWithRead(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
int file = _open(args->file_name.data, _O_BINARY | _O_RDONLY);
if (file != -1) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
char *dest = buffer.data;
u64 space_remaining = buffer.size;
while (space_remaining) {
u32 read_size = INT_MAX;
if ((u64)read_size > space_remaining)
read_size = (u32)space_remaining;
RepTester_BeginTime(tester);
int result = _read(file, dest, read_size);
RepTester_EndTime(tester);
if (result == (int)read_size) {
RepTester_CountBytes(tester, read_size);
} else {
RepTester_Error(tester, STR8("_read failed"));
break;
}
space_remaining -= read_size;
dest += read_size;
}
TestDealloc(args, &buffer);
_close(file);
} else {
RepTester_Error(tester, STR8("_open failed"));
}
}
}
static void ReadWithReadFile(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
HANDLE file = CreateFileA(args->file_name.data,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (file != INVALID_HANDLE_VALUE) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
char *dest = buffer.data;
u64 space_remaining = buffer.size;
while (space_remaining) {
u32 read_size = UINT32_MAX;
if ((u64)read_size > space_remaining)
read_size = (u32)space_remaining;
DWORD bytes_read = 0;
RepTester_BeginTime(tester);
BOOL result = ReadFile(file, dest, read_size, &bytes_read, 0);
RepTester_EndTime(tester);
if (result && (bytes_read == read_size))
RepTester_CountBytes(tester, read_size);
else
RepTester_Error(tester, STR8("ReadFile failed"));
space_remaining -= read_size;
dest += read_size;
}
TestDealloc(args, &buffer);
CloseHandle(file);
} else {
RepTester_Error(tester, STR8("CreateFileA failed"));
}
}
}
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;
TestFuncPtr *func;
} TestFunction;
int main(int argc, char const **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s [existing filename]\n", argv[0]);
return -1;
}
Str8 file_name = {.data = CAST(char *) argv[1], .size = strlen(argv[1])};
struct __stat64 stat;
_stat64(file_name.data, &stat);
ReadArgs args = {};
args.dest.data = VirtualAlloc(NULL, stat.st_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
args.dest.size = stat.st_size;
args.file_name = file_name;
if (stat.st_size <= 0) {
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
return -1;
}
if (!args.dest.data) {
fprintf(stderr, "ERROR: Failed to allocate %zu bytes\n", stat.st_size);
return -1;
}
InitializeOSMetrics();
TestFunction test_functions[] = {
{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 = 0; alloc_index < AllocType_Count; alloc_index++) {
RepTester *tester = &testers[func_index][alloc_index];
TestFunction test_func = test_functions[func_index];
printf("\n--- %.*s + %.*s ---\n",
STR8_FMT(AllocTypeStr8((AllocType)alloc_index)),
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.e[RepTesterValueType_CPUTimer] = (u64)-1;
} else if (tester->mode == RepTesterMode_Complete) {
tester->mode = RepTesterMode_Testing;
if (tester->desired_bytes_read != args.dest.size)
RepTester_Error(tester, STR8("desired_bytes_read changed"));
if (tester->cpu_timer_freq != cpu_timer_freq)
RepTester_Error(tester, STR8("cpu frequency changed"));
}
tester->run_duration = /*seconds_to_try*/ 10 * cpu_timer_freq;
tester->start_time = ReadCPUTimer();
args.alloc_type = alloc_index;
test_func.func(tester, &args);
}
}
}
return 0;
}
+52
View File
@@ -0,0 +1,52 @@
typedef enum RepTesterMode {
RepTesterMode_Nil,
RepTesterMode_Testing,
RepTesterMode_Error,
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 {
RepTesterValue total;
RepTesterValue max;
RepTesterValue min;
} RepTesterResults;
typedef struct RepTester {
RepTesterMode mode;
u32 open_block_count;
u32 close_block_count;
u64 cpu_timer_freq;
RepTesterResults results;
RepTesterValue accumulated_on_this_test;
size_t desired_bytes_read;
u64 start_time;
u64 run_duration;
} RepTester;
typedef enum AllocType {
AllocType_None,
AllocType_VirtualAlloc,
AllocType_Malloc,
AllocType_Count,
} AllocType;
typedef struct ReadArgs {
Buffer dest;
Str8 file_name;
AllocType alloc_type;
} ReadArgs;
BIN
View File
Binary file not shown.