Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c9ae6371 | |||
| 1b21b7c533 | |||
| 037e6e8d45 | |||
| 4b1e8720e3 | |||
| 65efb397ba | |||
| 8083a7c782 |
@@ -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
@@ -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
@@ -1,5 +1,4 @@
|
||||
// NOTE: Sim8086
|
||||
// ============================================================================
|
||||
// NOTE: Sim8086 ///////////////////////////////////////////////////////////////////////////////////
|
||||
typedef enum S86_OpDecodeType {
|
||||
S86_OpDecodeType_MOVRegOrMemToOrFromReg,
|
||||
S86_OpDecodeType_MOVImmediateToRegOrMem,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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");
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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, ...);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 = {};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
Binary file not shown.
Reference in New Issue
Block a user