Compare commits

..

28 Commits

Author SHA1 Message Date
doylet a9c9ae6371 perfaware/part3: Complete 61 Page faults (aggregate faults in each test) 2024-03-05 21:45:07 +11:00
doylet 1b21b7c533 Fix reading of exe file instead of passed in file 2024-03-05 00:17:14 +11:00
doylet 037e6e8d45 Add allocation type to the repetition test 2024-03-04 17:06:26 +11:00
doylet 4b1e8720e3 Add the repetition tester 2024-03-03 23:46:35 +11:00
doylet 65efb397ba Fix the build 2024-03-03 22:37:43 +11:00
doylet 8083a7c782 Prepare for part 3 2024-03-03 22:33:57 +11:00
doylet d01cf53ff8 perfaware/part2: Add bandwidth tracking 2023-08-24 22:14:24 +10:00
doylet 5817060a8b perfaware/part2: Add disable switch for profiler 2023-07-24 22:07:16 +10:00
doylet bc2da4df4c perfaware/part2: Incorporate recursive profiler changes 2023-07-16 22:58:26 +10:00
doylet 3e9d6980df perfaware/part2: Do profiling of nested blocks 2023-07-08 00:44:38 +10:00
doylet b91869a49e perfaware/part2: Do RDTSC homework 2023-07-04 21:40:08 +10:00
doylet 662f5afd9b perfaware/part2: Parse strings to f64, calc & validate haversine 2023-07-01 23:12:57 +10:00
doylet 1f9a9bdc21 perfaware/part2: Extract the strings containing the haversine points 2023-07-01 22:32:50 +10:00
doylet f8ac671e3a perfaware/part2: Setup haversine parser task 2023-06-30 21:30:31 +10:00
doylet 7dfd1b1927 perfaware/part2: Write the haversine input generator 2023-06-26 23:51:45 +10:00
doylet a4658b1951 perfaware: Setup part2 2023-06-25 22:30:56 +10:00
doylet 9db292f616 perfware/part1: Support listing 0057 2023-06-20 23:19:17 +10:00
doylet 56cc90c57b perfware/part1: Handle 8088 cycle counter exercise in listing 56 2023-06-14 22:56:27 +10:00
doylet fb286c7f11 perfware/part1: Handle 8086 cycle count exercise 2023-06-14 22:29:40 +10:00
doylet 16b8483312 perfware/part1: Start to add clock counts for MOV 2023-06-13 22:14:48 +10:00
doylet d888968ce0 perfaware/part1: Support listing 0055 2023-04-18 22:38:07 +10:00
doylet dd83866f2b perfaware/part1: Correctly load and execute program from 8086 1mb memory 2023-04-18 21:41:19 +10:00
doylet 35fe0b899a perfaware/part1: Support listing 0053" 2023-04-18 21:28:41 +10:00
doylet 39ec4213d6 perfaware/part1: Support listing 0051 & 0052 2023-04-17 23:33:31 +10:00
doylet bd3dc5f2bc perfaware/part1: Support listing 0050" 2023-04-16 23:13:04 +10:00
doylet 00ad047d9a perfaware/part1: Support listing 0049 2023-04-16 22:29:00 +10:00
doylet 6f0715b0ca perfaware/part1: Support listing 0048 2023-04-16 21:36:10 +10:00
doylet 20be5d0892 perfaware/part1: Build ASM for exec files and test, move prototype to header 2023-04-16 21:22:00 +10:00
100 changed files with 67733 additions and 324 deletions
+3 -1
View File
@@ -1 +1,3 @@
*/build
Build
part2/*.f64
part2/*.json
+350
View File
@@ -0,0 +1,350 @@
@echo off
REM Setup //////////////////////////////////////////////////////////////////////////////////////////
set script_dir_backslash=%~dp0
set script_dir=%script_dir_backslash:~0,-1%
set build_dir=%script_dir%\Build
set part1_dir=%script_dir%\part1
set part1_build_dir=%build_dir%\part1
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%
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 //////////////////////////////////////////////////////////////////////////////////////////
set listing_0037=listing_0037_single_register_mov
set part1_build_dir_listing_0037=%part1_build_dir%\%listing_0037%
copy /Y %part1_dir%\%listing_0037% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0037% > %part1_build_dir_listing_0037%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0038=listing_0038_many_register_mov
set part1_build_dir_listing_0038=%part1_build_dir%\%listing_0038%
copy /Y %part1_dir%\%listing_0038% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0038% > %part1_build_dir_listing_0038%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0039=listing_0039_more_movs
set part1_build_dir_listing_0039=%part1_build_dir%\%listing_0039%
copy /Y %part1_dir%\%listing_0039% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0039% > %part1_build_dir_listing_0039%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0040=listing_0040_challenge_movs
set part1_build_dir_listing_0040=%part1_build_dir%\%listing_0040%
copy /Y %part1_dir%\%listing_0040% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0040% > %part1_build_dir_listing_0040%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0041=listing_0041_add_sub_cmp_jnz
set part1_build_dir_listing_0041=%part1_build_dir%\%listing_0041%
copy /Y %part1_dir%\%listing_0041% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0041% > %part1_build_dir_listing_0041%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0042=listing_0042_completionist_decode
set part1_build_dir_listing_0042=%part1_build_dir%\%listing_0042%
copy /Y %part1_dir%\%listing_0042% %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0042% > %part1_build_dir_listing_0042%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0043=listing_0043_immediate_movs
set part1_build_dir_listing_0043=%part1_build_dir%\%listing_0043%
copy /Y %part1_dir%\%listing_0043% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0043%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0043% > %part1_build_dir_listing_0043%_disassembled.asm
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0043% > %part1_build_dir_listing_0043%_disassembled.txt
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0044=listing_0044_register_movs
set part1_build_dir_listing_0044=%part1_build_dir%\%listing_0044%
copy /Y %part1_dir%\%listing_0044% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0044%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0044% > %part1_build_dir_listing_0044%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0044% > %part1_build_dir_listing_0044%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0045=listing_0045_challenge_register_movs
set part1_build_dir_listing_0045=%part1_build_dir%\%listing_0045%
copy /Y %part1_dir%\%listing_0045% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0045%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0045% > %part1_build_dir_listing_0045%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0045% > %part1_build_dir_listing_0045%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0046=listing_0046_add_sub_cmp
set part1_build_dir_listing_0046=%part1_build_dir%\%listing_0046%
copy /Y %part1_dir%\%listing_0046% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0046%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0046% > %part1_build_dir_listing_0046%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0046% > %part1_build_dir_listing_0046%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0047=listing_0047_challenge_flags
set part1_build_dir_listing_0047=%part1_build_dir%\%listing_0047%
copy /Y %part1_dir%\%listing_0047% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0047%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0047% > %part1_build_dir_listing_0047%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0047% > %part1_build_dir_listing_0047%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0048=listing_0048_ip_register
set part1_build_dir_listing_0048=%part1_build_dir%\%listing_0048%
copy /Y %part1_dir%\%listing_0048% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0048%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0048% > %part1_build_dir_listing_0048%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0048% > %part1_build_dir_listing_0048%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0049=listing_0049_conditional_jumps
set part1_build_dir_listing_0049=%part1_build_dir%\%listing_0049%
copy /Y %part1_dir%\%listing_0049% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0049%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0049% > %part1_build_dir_listing_0049%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0049% > %part1_build_dir_listing_0049%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0050=listing_0050_challenge_jumps
set part1_build_dir_listing_0050=%part1_build_dir%\%listing_0050%
copy /Y %part1_dir%\%listing_0050% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0050%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0050% > %part1_build_dir_listing_0050%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0050% > %part1_build_dir_listing_0050%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0051=listing_0051_memory_mov
set part1_build_dir_listing_0051=%part1_build_dir%\%listing_0051%
copy /Y %part1_dir%\%listing_0051% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0051%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0051% > %part1_build_dir_listing_0051%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0051% > %part1_build_dir_listing_0051%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0052=listing_0052_memory_add_loop
set part1_build_dir_listing_0052=%part1_build_dir%\%listing_0052%
copy /Y %part1_dir%\%listing_0052% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0052%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0052% > %part1_build_dir_listing_0052%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0052% > %part1_build_dir_listing_0052%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0053=listing_0053_add_loop_challenge
set part1_build_dir_listing_0053=%part1_build_dir%\%listing_0053%
copy /Y %part1_dir%\%listing_0053% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0053%.txt %part1_build_dir% 1>NUL
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0053% > %part1_build_dir_listing_0053%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0053% > %part1_build_dir_listing_0053%_disassembled.asm
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0054=listing_0054_draw_rectangle
set part1_build_dir_listing_0054=%part1_build_dir%\%listing_0054%
copy /Y %part1_dir%\%listing_0054% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0054%.txt %part1_build_dir% 1>NUL
pushd %part1_build_dir%
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --dump %part1_build_dir_listing_0054% > %part1_build_dir_listing_0054%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0054% > %part1_build_dir_listing_0054%_disassembled.asm
popd
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0055=listing_0055_challenge_rectangle
set part1_build_dir_listing_0055=%part1_build_dir%\%listing_0055%
copy /Y %part1_dir%\%listing_0055% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0055%.txt %part1_build_dir% 1>NUL
pushd %part1_build_dir%
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --dump %part1_build_dir_listing_0055% > %part1_build_dir_listing_0055%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0055% > %part1_build_dir_listing_0055%_disassembled.asm
popd
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0056=listing_0056_estimating_cycles
set part1_build_dir_listing_0056=%part1_build_dir%\%listing_0056%
copy /Y %part1_dir%\%listing_0056% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0056%.txt %part1_build_dir% 1>NUL
pushd %part1_build_dir%
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8086 %part1_build_dir_listing_0056% > %part1_build_dir_listing_0056%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0056% > %part1_build_dir_listing_0056%_disassembled.asm
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8088 %part1_build_dir_listing_0056% >> %part1_build_dir_listing_0056%_disassembled.txt
popd
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 ////////////////////////////////////////////////////////////////////////////////////////////////
set listing_0057=listing_0057_challenge_cycles
set part1_build_dir_listing_0057=%part1_build_dir%\%listing_0057%
copy /Y %part1_dir%\%listing_0057% %part1_build_dir% 1>NUL
copy /Y %part1_dir%\%listing_0057%.txt %part1_build_dir% 1>NUL
pushd %part1_build_dir%
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8086 %part1_build_dir_listing_0057% > %part1_build_dir_listing_0057%_disassembled.txt
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0057% > %part1_build_dir_listing_0057%_disassembled.asm
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8088 %part1_build_dir_listing_0057% >> %part1_build_dir_listing_0057%_disassembled.txt
popd
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 //////////////////////////////////////////////////////////////////////////////////////////
:part2
pushd %part2_build_dir%
cl %part2_dir%\haversine_generator.c /W4 /WX /Z7 /nologo /Fe:haversine_generator_debug || exit /b 1
cl %part2_dir%\haversine_generator.c /W4 /WX /Z7 /nologo /O2 /Fe:haversine_generator_release || exit /b 1
cl %part2_dir%\haversine_generator.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /Fe:haversine_generator_profiled_debug || exit /b 1
cl %part2_dir%\haversine_generator.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /O2 /Fe:haversine_generator_profiled_release || exit /b 1
cl %part2_dir%\haversine.c /W4 /WX /Z7 /nologo /Fe:haversine_debug || exit /b 1
cl %part2_dir%\haversine.c /W4 /WX /Z7 /nologo /O2 /Fe:haversine_release || exit /b 1
cl %part2_dir%\haversine.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /Fe:haversine_profiled_debug || exit /b 1
cl %part2_dir%\haversine.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /O2 /Fe:haversine_profiled_release || exit /b 1
cl %part2_dir%\listing_0071_os_timer_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0071_os_timer_main_release || exit /b 1
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
-94
View File
@@ -1,94 +0,0 @@
@echo off
REM Setup
REM ===========================================================================
set script_dir_backslash=%~dp0
set script_dir=%script_dir_backslash:~0,-1%
set build_dir=%script_dir%\build
if not exist %build_dir% mkdir %build_dir%
REM Build
REM ===========================================================================
pushd %build_dir%
cl %script_dir%\sim8086.c /W4 /WX /Z7 /nologo || exit /b 1
popd
REM Tests
REM ===========================================================================
set listing_0037=listing_0037_single_register_mov
copy /Y %script_dir%\%listing_0037% %build_dir% 1>NUL
set build_dir_listing_0037=%build_dir%\%listing_0037%
%build_dir%\sim8086.exe %build_dir_listing_0037% > %build_dir_listing_0037%_disassembled.asm
nasm %build_dir_listing_0037%_disassembled.asm
fc /B %build_dir_listing_0037% %build_dir_listing_0037%_disassembled || exit /b 1
set listing_0038=listing_0038_many_register_mov
copy /Y %script_dir%\%listing_0038% %build_dir% 1>NUL
set build_dir_listing_0038=%build_dir%\%listing_0038%
%build_dir%\sim8086.exe %build_dir_listing_0038% > %build_dir_listing_0038%_disassembled.asm
nasm %build_dir_listing_0038%_disassembled.asm
fc /B %build_dir_listing_0038% %build_dir_listing_0038%_disassembled || exit /b 1
set listing_0039=listing_0039_more_movs
copy /Y %script_dir%\%listing_0039% %build_dir% 1>NUL
set build_dir_listing_0039=%build_dir%\%listing_0039%
%build_dir%\sim8086.exe %build_dir_listing_0039% > %build_dir_listing_0039%_disassembled.asm
nasm %build_dir_listing_0039%_disassembled.asm
fc /B %build_dir_listing_0039% %build_dir_listing_0039%_disassembled || exit /b 1
set listing_0040=listing_0040_challenge_movs
copy /Y %script_dir%\%listing_0040% %build_dir% 1>NUL
set build_dir_listing_0040=%build_dir%\%listing_0040%
%build_dir%\sim8086.exe %build_dir_listing_0040% > %build_dir_listing_0040%_disassembled.asm
nasm %build_dir_listing_0040%_disassembled.asm
fc /B %build_dir_listing_0040% %build_dir_listing_0040%_disassembled || exit /b 1
set listing_0041=listing_0041_add_sub_cmp_jnz
copy /Y %script_dir%\%listing_0041% %build_dir% 1>NUL
set build_dir_listing_0041=%build_dir%\%listing_0041%
%build_dir%\sim8086.exe %build_dir_listing_0041% > %build_dir_listing_0041%_disassembled.asm
nasm %build_dir_listing_0041%_disassembled.asm
fc /B %build_dir_listing_0041% %build_dir_listing_0041%_disassembled || exit /b 1
set listing_0042=listing_0042_completionist_decode
copy /Y %script_dir%\%listing_0042% %build_dir% 1>NUL
set build_dir_listing_0042=%build_dir%\%listing_0042%
%build_dir%\sim8086.exe %build_dir_listing_0042% > %build_dir_listing_0042%_disassembled.asm
nasm %build_dir_listing_0042%_disassembled.asm
fc /B %build_dir_listing_0042% %build_dir_listing_0042%_disassembled || exit /b 1
set listing_0043=listing_0043_immediate_movs
copy /Y %script_dir%\%listing_0043% %build_dir% 1>NUL
copy /Y %script_dir%\%listing_0043%.txt %build_dir% 1>NUL
set build_dir_listing_0043=%build_dir%\%listing_0043%
%build_dir%\sim8086.exe --exec %build_dir_listing_0043% > %build_dir_listing_0043%_disassembled.txt
fc /N %build_dir_listing_0043%.txt %build_dir_listing_0043%_disassembled.txt || exit /b 1
set listing_0044=listing_0044_register_movs
copy /Y %script_dir%\%listing_0044% %build_dir% 1>NUL
copy /Y %script_dir%\%listing_0044%.txt %build_dir% 1>NUL
set build_dir_listing_0044=%build_dir%\%listing_0044%
%build_dir%\sim8086.exe --exec %build_dir_listing_0044% > %build_dir_listing_0044%_disassembled.txt
fc /N %build_dir_listing_0044%.txt %build_dir_listing_0044%_disassembled.txt || exit /b 1
set listing_0045=listing_0045_challenge_register_movs
copy /Y %script_dir%\%listing_0045% %build_dir% 1>NUL
copy /Y %script_dir%\%listing_0045%.txt %build_dir% 1>NUL
set build_dir_listing_0045=%build_dir%\%listing_0045%
%build_dir%\sim8086.exe --exec %build_dir_listing_0045% > %build_dir_listing_0045%_disassembled.txt
fc /N %build_dir_listing_0045%.txt %build_dir_listing_0045%_disassembled.txt || exit /b 1
set listing_0045=listing_0045_challenge_register_movs
set listing_0046=listing_0046_add_sub_cmp
copy /Y %script_dir%\%listing_0046% %build_dir% 1>NUL
copy /Y %script_dir%\%listing_0046%.txt %build_dir% 1>NUL
set build_dir_listing_0046=%build_dir%\%listing_0046%
%build_dir%\sim8086.exe --exec %build_dir_listing_0046% > %build_dir_listing_0046%_disassembled.txt
fc /N %build_dir_listing_0046%.txt %build_dir_listing_0046%_disassembled.txt || exit /b 1
set listing_0047=listing_0047_challenge_flags
copy /Y %script_dir%\%listing_0047% %build_dir% 1>NUL
copy /Y %script_dir%\%listing_0047%.txt %build_dir% 1>NUL
set build_dir_listing_0047=%build_dir%\%listing_0047%
%build_dir%\sim8086.exe --exec %build_dir_listing_0047% > %build_dir_listing_0047%_disassembled.txt
fc /N %build_dir_listing_0047%.txt %build_dir_listing_0047%_disassembled.txt || exit /b 1
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 48
; ========================================================================
bits 16
mov cx, 200
mov bx, cx
add cx, 1000
mov bx, 2000
sub cx, bx
+13
View File
@@ -0,0 +1,13 @@
--- test\listing_0048_ip_register execution ---
mov cx, 200 ; cx:0x0->0xc8 ip:0x0->0x3
mov bx, cx ; bx:0x0->0xc8 ip:0x3->0x5
add cx, 1000 ; cx:0xc8->0x4b0 ip:0x5->0x9 flags:->A
mov bx, 2000 ; bx:0xc8->0x7d0 ip:0x9->0xc
sub cx, bx ; cx:0x4b0->0xfce0 ip:0xc->0xe flags:A->CS
Final registers:
bx: 0x07d0 (2000)
cx: 0xfce0 (64736)
ip: 0x000e (14)
flags: CS
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 49
; ========================================================================
bits 16
mov cx, 3
mov bx, 1000
loop_start:
add bx, 10
sub cx, 1
jnz loop_start
+18
View File
@@ -0,0 +1,18 @@
--- test\listing_0049_conditional_jumps execution ---
mov cx, 3 ; cx:0x0->0x3 ip:0x0->0x3
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x3->0x6
add bx, 10 ; bx:0x3e8->0x3f2 ip:0x6->0x9 flags:->A
sub cx, 1 ; cx:0x3->0x2 ip:0x9->0xc flags:A->
jne $-6 ; ip:0xc->0x6
add bx, 10 ; bx:0x3f2->0x3fc ip:0x6->0x9 flags:->P
sub cx, 1 ; cx:0x2->0x1 ip:0x9->0xc flags:P->
jne $-6 ; ip:0xc->0x6
add bx, 10 ; bx:0x3fc->0x406 ip:0x6->0x9 flags:->PA
sub cx, 1 ; cx:0x1->0x0 ip:0x9->0xc flags:PA->PZ
jne $-6 ; ip:0xc->0xe
Final registers:
bx: 0x0406 (1030)
ip: 0x000e (14)
flags: PZ
Binary file not shown.
+38
View File
@@ -0,0 +1,38 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 50
; ========================================================================
bits 16
mov ax, 10
mov bx, 10
mov cx, 10
label_0:
cmp bx, cx
je label_1
add ax, 1
jp label_2
label_1:
sub bx, 5
jb label_3
label_2:
sub cx, 2
label_3:
loopnz label_0
+38
View File
@@ -0,0 +1,38 @@
--- test\listing_0050_challenge_jumps execution ---
mov ax, 10 ; ax:0x0->0xa ip:0x0->0x3
mov bx, 10 ; bx:0x0->0xa ip:0x3->0x6
mov cx, 10 ; cx:0x0->0xa ip:0x6->0x9
cmp bx, cx ; ip:0x9->0xb flags:->PZ
je $+7 ; ip:0xb->0x12
sub bx, 5 ; bx:0xa->0x5 ip:0x12->0x15 flags:PZ->P
jb $+5 ; ip:0x15->0x17
sub cx, 2 ; cx:0xa->0x8 ip:0x17->0x1a flags:P->
loopnz $-17 ; cx:0x8->0x7 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:->CAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xa->0xb ip:0xd->0x10 flags:CAS->
jp $+7 ; ip:0x10->0x12
sub bx, 5 ; bx:0x5->0x0 ip:0x12->0x15 flags:->PZ
jb $+5 ; ip:0x15->0x17
sub cx, 2 ; cx:0x7->0x5 ip:0x17->0x1a flags:PZ->P
loopnz $-17 ; cx:0x5->0x4 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:P->CPAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xb->0xc ip:0xd->0x10 flags:CPAS->P
jp $+7 ; ip:0x10->0x17
sub cx, 2 ; cx:0x4->0x2 ip:0x17->0x1a flags:P->
loopnz $-17 ; cx:0x2->0x1 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:->CPAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xc->0xd ip:0xd->0x10 flags:CPAS->
jp $+7 ; ip:0x10->0x12
sub bx, 5 ; bx:0x0->0xfffb ip:0x12->0x15 flags:->CAS
jb $+5 ; ip:0x15->0x1a
loopnz $-17 ; cx:0x1->0x0 ip:0x1a->0x1c
Final registers:
ax: 0x000d (13)
bx: 0xfffb (65531)
ip: 0x001c (28)
flags: CAS
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 51
; ========================================================================
bits 16
mov word [1000], 1
mov word [1002], 2
mov word [1004], 3
mov word [1006], 4
mov bx, 1000
mov word [bx + 4], 10
mov bx, word [1000]
mov cx, word [1002]
mov dx, word [1004]
mov bp, word [1006]
+19
View File
@@ -0,0 +1,19 @@
--- test\listing_0051_memory_mov execution ---
mov word [+1000], 1 ; ip:0x0->0x6
mov word [+1002], 2 ; ip:0x6->0xc
mov word [+1004], 3 ; ip:0xc->0x12
mov word [+1006], 4 ; ip:0x12->0x18
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x18->0x1b
mov word [bx+4], 10 ; ip:0x1b->0x20
mov bx, [+1000] ; bx:0x3e8->0x1 ip:0x20->0x24
mov cx, [+1002] ; cx:0x0->0x2 ip:0x24->0x28
mov dx, [+1004] ; dx:0x0->0xa ip:0x28->0x2c
mov bp, [+1006] ; bp:0x0->0x4 ip:0x2c->0x30
Final registers:
bx: 0x0001 (1)
cx: 0x0002 (2)
dx: 0x000a (10)
bp: 0x0004 (4)
ip: 0x0030 (48)
Binary file not shown.
+36
View File
@@ -0,0 +1,36 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 52
; ========================================================================
bits 16
mov dx, 6
mov bp, 1000
mov si, 0
init_loop_start:
mov word [bp + si], si
add si, 2
cmp si, dx
jnz init_loop_start
mov bx, 0
mov si, 0
add_loop_start:
mov cx, word [bp + si]
add bx, cx
add si, 2
cmp si, dx
jnz add_loop_start
+43
View File
@@ -0,0 +1,43 @@
--- test\listing_0052_memory_add_loop execution ---
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
mov si, 0 ; ip:0x6->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
cmp si, dx ; ip:0xe->0x10 flags:->CAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
jne $-7 ; ip:0x10->0x12
mov bx, 0 ; ip:0x12->0x15
mov si, 0 ; si:0x6->0x0 ip:0x15->0x18
mov cx, [bp+si] ; ip:0x18->0x1a
add bx, cx ; ip:0x1a->0x1c
add si, 2 ; si:0x0->0x2 ip:0x1c->0x1f flags:PZ->
cmp si, dx ; ip:0x1f->0x21 flags:->CPAS
jne $-9 ; ip:0x21->0x18
mov cx, [bp+si] ; cx:0x0->0x2 ip:0x18->0x1a
add bx, cx ; bx:0x0->0x2 ip:0x1a->0x1c flags:CPAS->
add si, 2 ; si:0x2->0x4 ip:0x1c->0x1f
cmp si, dx ; ip:0x1f->0x21 flags:->CAS
jne $-9 ; ip:0x21->0x18
mov cx, [bp+si] ; cx:0x2->0x4 ip:0x18->0x1a
add bx, cx ; bx:0x2->0x6 ip:0x1a->0x1c flags:CAS->P
add si, 2 ; si:0x4->0x6 ip:0x1c->0x1f
cmp si, dx ; ip:0x1f->0x21 flags:P->PZ
jne $-9 ; ip:0x21->0x23
Final registers:
bx: 0x0006 (6)
cx: 0x0004 (4)
dx: 0x0006 (6)
bp: 0x03e8 (1000)
si: 0x0006 (6)
ip: 0x0023 (35)
flags: PZ
Binary file not shown.
+35
View File
@@ -0,0 +1,35 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 53
; ========================================================================
bits 16
mov dx, 6
mov bp, 1000
mov si, 0
init_loop_start:
mov word [bp + si], si
add si, 2
cmp si, dx
jnz init_loop_start
mov bx, 0
mov si, dx
sub bp, 2
add_loop_start:
add bx, word [bp + si]
sub si, 2
jnz add_loop_start
+36
View File
@@ -0,0 +1,36 @@
--- test\listing_0053_add_loop_challenge execution ---
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
mov si, 0 ; ip:0x6->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
cmp si, dx ; ip:0xe->0x10 flags:->CAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
jne $-7 ; ip:0x10->0x12
mov bx, 0 ; ip:0x12->0x15
mov si, dx ; ip:0x15->0x17
sub bp, 2 ; bp:0x3e8->0x3e6 ip:0x17->0x1a flags:PZ->
add bx, [bp+si] ; bx:0x0->0x4 ip:0x1a->0x1c
sub si, 2 ; si:0x6->0x4 ip:0x1c->0x1f
jne $-5 ; ip:0x1f->0x1a
add bx, [bp+si] ; bx:0x4->0x6 ip:0x1a->0x1c flags:->P
sub si, 2 ; si:0x4->0x2 ip:0x1c->0x1f flags:P->
jne $-5 ; ip:0x1f->0x1a
add bx, [bp+si] ; ip:0x1a->0x1c flags:->P
sub si, 2 ; si:0x2->0x0 ip:0x1c->0x1f flags:P->PZ
jne $-5 ; ip:0x1f->0x21
Final registers:
bx: 0x0006 (6)
dx: 0x0006 (6)
bp: 0x03e6 (998)
ip: 0x0021 (33)
flags: PZ
Binary file not shown.
+43
View File
@@ -0,0 +1,43 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 54
; ========================================================================
bits 16
; Start image after one row, to avoid overwriting our code!
mov bp, 64*4
mov dx, 0
y_loop_start:
mov cx, 0
x_loop_start:
; Fill pixel
mov word [bp + 0], cx ; Red
mov word [bp + 2], dx ; Blue
mov byte [bp + 3], 255 ; Alpha
; Advance pixel location
add bp, 4
; Advance X coordinate and loop
add cx, 1
cmp cx, 64
jnz x_loop_start
; Advance Y coordinate and loop
add dx, 1
cmp dx, 64
jnz y_loop_start
File diff suppressed because it is too large Load Diff
Binary file not shown.
@@ -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 further information
;
; ========================================================================
; ========================================================================
; LISTING 55
; ========================================================================
bits 16
; Start image after one row, to avoid overwriting our code!
mov bp, 64*4
; Draw the solid rectangle red/blue/alpha
mov dx, 64
y_loop_start:
mov cx, 64
x_loop_start:
mov byte [bp + 0], cl ; Red
mov byte [bp + 1], 0 ; Green
mov byte [bp + 2], dl ; Blue
mov byte [bp + 3], 255 ; Alpha
add bp, 4
loop x_loop_start
sub dx, 1
jnz y_loop_start
; Add the line rectangle green
mov bp, 64*4 + 4*64 + 4
mov bx, bp
mov cx, 62
outline_loop_start:
mov byte [bp + 1], 255 ; Top line
mov byte [bp + 61*64*4 + 1], 255 ; Bottom line
mov byte [bx + 1], 255 ; Left line
mov byte [bx + 61*4 + 1], 255 ; Right line
add bp, 4
add bx, 4*64
loop outline_loop_start
File diff suppressed because it is too large Load Diff
Binary file not shown.
+41
View File
@@ -0,0 +1,41 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 56
; ========================================================================
bits 16
mov bx, 1000
mov bp, 2000
mov si, 3000
mov di, 4000
mov cx, bx
mov dx, 12
mov dx, [1000]
mov cx, [bx]
mov cx, [bp]
mov [si], cx
mov [di], cx
mov cx, [bx + 1000]
mov cx, [bp + 1000]
mov [si + 1000], cx
mov [di + 1000], cx
add cx, dx
add [di + 1000], cx
add dx, 50
+72
View File
@@ -0,0 +1,72 @@
**************
**** 8086 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0056_estimating_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
mov dx, [+1000] ; Clocks: +14 = 36 (8 + 6ea) | dx:0xc->0x0 ip:0x11->0x15
mov cx, [bx] ; Clocks: +13 = 49 (8 + 5ea) | cx:0x3e8->0x0 ip:0x15->0x17
mov cx, [bp] ; Clocks: +13 = 62 (8 + 5ea) | ip:0x17->0x1a
mov word [si], cx ; Clocks: +14 = 76 (9 + 5ea) | ip:0x1a->0x1c
mov word [di], cx ; Clocks: +14 = 90 (9 + 5ea) | ip:0x1c->0x1e
mov cx, [bx+1000] ; Clocks: +17 = 107 (8 + 9ea) | ip:0x1e->0x22
mov cx, [bp+1000] ; Clocks: +17 = 124 (8 + 9ea) | ip:0x22->0x26
mov word [si+1000], cx ; Clocks: +18 = 142 (9 + 9ea) | ip:0x26->0x2a
mov word [di+1000], cx ; Clocks: +18 = 160 (9 + 9ea) | ip:0x2a->0x2e
add cx, dx ; Clocks: +3 = 163 | ip:0x2e->0x30 flags:->PZ
add word [di+1000], cx ; Clocks: +25 = 188 (16 + 9ea) | ip:0x30->0x34
add dx, 50 ; Clocks: +4 = 192 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
Final registers:
bx: 0x03e8 (1000)
dx: 0x0032 (50)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0037 (55)
**************
**** 8088 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0056_estimating_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
mov dx, [+1000] ; Clocks: +18 = 40 (8 + 6ea + 4p) | dx:0xc->0x0 ip:0x11->0x15
mov cx, [bx] ; Clocks: +17 = 57 (8 + 5ea + 4p) | cx:0x3e8->0x0 ip:0x15->0x17
mov cx, [bp] ; Clocks: +17 = 74 (8 + 5ea + 4p) | ip:0x17->0x1a
mov word [si], cx ; Clocks: +18 = 92 (9 + 5ea + 4p) | ip:0x1a->0x1c
mov word [di], cx ; Clocks: +18 = 110 (9 + 5ea + 4p) | ip:0x1c->0x1e
mov cx, [bx+1000] ; Clocks: +21 = 131 (8 + 9ea + 4p) | ip:0x1e->0x22
mov cx, [bp+1000] ; Clocks: +21 = 152 (8 + 9ea + 4p) | ip:0x22->0x26
mov word [si+1000], cx ; Clocks: +22 = 174 (9 + 9ea + 4p) | ip:0x26->0x2a
mov word [di+1000], cx ; Clocks: +22 = 196 (9 + 9ea + 4p) | ip:0x2a->0x2e
add cx, dx ; Clocks: +3 = 199 | ip:0x2e->0x30 flags:->PZ
add word [di+1000], cx ; Clocks: +33 = 232 (16 + 9ea + 8p) | ip:0x30->0x34
add dx, 50 ; Clocks: +4 = 236 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
Final registers:
bx: 0x03e8 (1000)
dx: 0x0032 (50)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0037 (55)
+2
View File
@@ -0,0 +1,2 @@
»иЅРѕё ї  
‰ ‹‹и‰€и‹Љи‰‰и’иѓL’й•зѓK
+42
View File
@@ -0,0 +1,42 @@
; ========================================================================
;
; (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 further information
;
; ========================================================================
; ========================================================================
; LISTING 57
; ========================================================================
bits 16
mov bx, 1000
mov bp, 2000
mov si, 3000
mov di, 4000
mov cx, [bp + di]
mov [bx + si], cx
mov cx, [bp + si]
mov [bx + di], cx
mov cx, [bp + di + 1000]
mov [bx + si + 1000], cx
mov cx, [bp + si + 1000]
mov [bx + di + 1000], cx
add dx, [bp + si + 1000]
add word [bp + si], 76
add dx, [bp + si + 1001]
add [di + 999], dx
add word [bp + si], 75
+70
View File
@@ -0,0 +1,70 @@
**************
**** 8086 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0057_challenge_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, [bp+di] ; Clocks: +15 = 31 (8 + 7ea) | ip:0xc->0xe
mov word [bx+si], cx ; Clocks: +16 = 47 (9 + 7ea) | ip:0xe->0x10
mov cx, [bp+si] ; Clocks: +16 = 63 (8 + 8ea) | ip:0x10->0x12
mov word [bx+di], cx ; Clocks: +17 = 80 (9 + 8ea) | ip:0x12->0x14
mov cx, [bp+di+1000] ; Clocks: +19 = 99 (8 + 11ea) | ip:0x14->0x18
mov word [bx+si+1000], cx ; Clocks: +20 = 119 (9 + 11ea) | ip:0x18->0x1c
mov cx, [bp+si+1000] ; Clocks: +20 = 139 (8 + 12ea) | ip:0x1c->0x20
mov word [bx+di+1000], cx ; Clocks: +21 = 160 (9 + 12ea) | ip:0x20->0x24
add dx, [bp+si+1000] ; Clocks: +21 = 181 (9 + 12ea) | ip:0x24->0x28 flags:->PZ
add word [bp+si], 76 ; Clocks: +25 = 206 (17 + 8ea) | ip:0x28->0x2b flags:PZ->
add dx, [bp+si+1001] ; Clocks: +25 = 231 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
add word [di+999], dx ; Clocks: +33 = 264 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
add word [bp+si], 75 ; Clocks: +25 = 289 (17 + 8ea) | ip:0x33->0x36 flags:P->A
Final registers:
bx: 0x03e8 (1000)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0036 (54)
flags: A
**************
**** 8088 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0057_challenge_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, [bp+di] ; Clocks: +19 = 35 (8 + 7ea + 4p) | ip:0xc->0xe
mov word [bx+si], cx ; Clocks: +20 = 55 (9 + 7ea + 4p) | ip:0xe->0x10
mov cx, [bp+si] ; Clocks: +20 = 75 (8 + 8ea + 4p) | ip:0x10->0x12
mov word [bx+di], cx ; Clocks: +21 = 96 (9 + 8ea + 4p) | ip:0x12->0x14
mov cx, [bp+di+1000] ; Clocks: +23 = 119 (8 + 11ea + 4p) | ip:0x14->0x18
mov word [bx+si+1000], cx ; Clocks: +24 = 143 (9 + 11ea + 4p) | ip:0x18->0x1c
mov cx, [bp+si+1000] ; Clocks: +24 = 167 (8 + 12ea + 4p) | ip:0x1c->0x20
mov word [bx+di+1000], cx ; Clocks: +25 = 192 (9 + 12ea + 4p) | ip:0x20->0x24
add dx, [bp+si+1000] ; Clocks: +25 = 217 (9 + 12ea + 4p) | ip:0x24->0x28 flags:->PZ
add word [bp+si], 76 ; Clocks: +33 = 250 (17 + 8ea + 8p) | ip:0x28->0x2b flags:PZ->
add dx, [bp+si+1001] ; Clocks: +25 = 275 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
add word [di+999], dx ; Clocks: +33 = 308 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
add word [bp+si], 75 ; Clocks: +33 = 341 (17 + 8ea + 8p) | ip:0x33->0x36 flags:P->A
Final registers:
bx: 0x03e8 (1000)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0036 (54)
flags: A
+553 -201
View File
File diff suppressed because it is too large Load Diff
+82 -9
View File
@@ -1,5 +1,4 @@
// NOTE: Sim8086
// ============================================================================
// NOTE: Sim8086 ///////////////////////////////////////////////////////////////////////////////////
typedef enum S86_OpDecodeType {
S86_OpDecodeType_MOVRegOrMemToOrFromReg,
S86_OpDecodeType_MOVImmediateToRegOrMem,
@@ -307,6 +306,7 @@ typedef enum S86_MnemonicOp {
S86_MnemonicOp_DirectInterSegment,
S86_MnemonicOp_Jump,
S86_MnemonicOp_Count,
} S86_MnemonicOp;
typedef enum S86_EffectiveAddress {
@@ -315,32 +315,105 @@ typedef enum S86_EffectiveAddress {
S86_EffectiveAddress_Dest,
} S86_EffectiveAddress;
typedef enum S86_WidePrefix {
S86_WidePrefix_None,
S86_WidePrefix_Src,
S86_WidePrefix_Dest,
} S86_WidePrefix;
typedef enum S86_WordBytePrefix {
S86_WordBytePrefix_None,
S86_WordBytePrefix_Byte,
S86_WordBytePrefix_Word,
} S86_WordBytePrefix;
typedef struct S86_Opcode {
S86_OpDecodeType type;
uint8_t byte_size; ///< Number of bytes used to encode this opcode
S86_Mnemonic mnemonic; ///< Mnemonic type
S86_EffectiveAddress effective_addr; ///< Src/dest op is an effective address calculation
bool effective_addr_loads_mem; ///< Effective address uses '[]' notation to load address memory
bool lock_prefix; ///< Prefix the opcode with "lock" instruction
bool rep_prefix; ///< Prefix the opcode with "rep" instruction
bool wide; ///< Opcode has the 'w' flag set
S86_WidePrefix wide_prefix; ///< Mnemonic src/dest op requires a 'word' or 'byte' prefix (e.g. ambiguous immediate size)
S86_WordBytePrefix word_byte_prefix; ///< Opcode has the 'word' or 'byte' prefix
S86_MnemonicOp src; ///< Source op for the mnemonic
S86_MnemonicOp dest; ///< Destination op for the mnemonic
int32_t displacement; ///< Opcode has displacement/data/offset
int32_t immediate; ///< Immediate value when src/dest op is an immediate
bool immediate_is_8bit; ///< Immediate was 8bit and sign extended
S86_MnemonicOp seg_reg_prefix; ///< Segment register that should prefix the upcoming instruction
uint32_t base_clocks; ///< Number of cycles required to complete this operation
uint32_t effective_address_clocks; ///< Number of cycles required to complete this operation
uint32_t transfer_penalty_clocks; ///< Number of extra cycles required to complete a word transfer
} S86_Opcode;
typedef enum S86_RegisterByte {
S86_RegisterByte_Lo,
S86_RegisterByte_Hi,
S86_RegisterByte_Count,
S86_RegisterByte_Nil,
} S86_RegisterByte;
typedef union S86_Register16 {
uint16_t word;
uint8_t bytes[S86_RegisterByte_Count];
} S86_Register16;
typedef struct S86_RegisterFileFlags {
bool carry;
bool zero;
bool sign;
bool overflow;
bool parity;
bool auxiliary_carry;
} S86_RegisterFileFlags;
typedef enum S86_RegisterFileRegArray {
S86_RegisterFileRegArray_AX,
S86_RegisterFileRegArray_BX,
S86_RegisterFileRegArray_CX,
S86_RegisterFileRegArray_DX,
S86_RegisterFileRegArray_SP,
S86_RegisterFileRegArray_BP,
S86_RegisterFileRegArray_SI,
S86_RegisterFileRegArray_DI,
S86_RegisterFileRegArray_ES,
S86_RegisterFileRegArray_CS,
S86_RegisterFileRegArray_SS,
S86_RegisterFileRegArray_DS,
S86_RegisterFileRegArray_Count,
} S86_RegisterFileRegArray;
typedef struct S86_RegisterFile {
S86_RegisterFileFlags flags;
uint16_t instruction_ptr;
union {
struct {
S86_Register16 ax;
S86_Register16 bx;
S86_Register16 cx;
S86_Register16 dx;
S86_Register16 sp;
S86_Register16 bp;
S86_Register16 si;
S86_Register16 di;
S86_Register16 es;
S86_Register16 cs;
S86_Register16 ss;
S86_Register16 ds;
} file;
S86_Register16 array[S86_RegisterFileRegArray_Count];
} reg;
} S86_RegisterFile;
bool S86_RegisterFileFlagsEq (S86_RegisterFileFlags lhs, S86_RegisterFileFlags rhs);
S86_Str8 S86_MnemonicStr8 (S86_Mnemonic type);
S86_MnemonicOp S86_MnemonicOpFromWReg (bool w, uint8_t reg);
S86_MnemonicOp S86_MnemonicOpFromSR (uint8_t sr);
S86_Str8 S86_MnemonicOpStr8 (S86_MnemonicOp type);
void S86_PrintOpcodeMnemonicOp(S86_Opcode opcode, bool src);
bool S86_MnemonicOpIsAccumulator (S86_MnemonicOp type);
bool S86_MnemonicOpIsRegister (S86_MnemonicOp type);
S86_Str8 S86_RegisterFileRegArrayStr8(S86_RegisterFileRegArray type);
void S86_PrintOpcodeMnemonicOp (S86_Opcode opcode, bool src);
void S86_PrintOpcode (S86_Opcode opcode);
void S86_DecodeEffectiveAddr (S86_Opcode *opcode, S86_BufferIterator *it, uint8_t rm, uint8_t mod, uint8_t w);
+36
View File
@@ -111,6 +111,42 @@ void S86_FileFree(S86_Buffer buffer)
VirtualFree(buffer.data, 0, MEM_RELEASE);
}
bool S86_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*/ S86_CAST(DWORD)buffer_size,
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
S86_ASSERT(bytes_written == buffer_size);
result = write_file_result && bytes_written == buffer_size;
CloseHandle(file_handle);
return result;
};
void S86_Print(S86_Str8 string)
{
if (s86_globals.stdout_handle == NULL) {
+1
View File
@@ -53,6 +53,7 @@ uint8_t S86_BufferIteratorNextByte(S86_BufferIterator *it);
// ============================================================================
S86_Buffer S86_FileRead(char const *file_path);
void S86_FileFree(S86_Buffer buffer);
bool S86_FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
// NOTE: Print
// ============================================================================
+173
View File
@@ -0,0 +1,173 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#include "haversine_stdlib.h"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "haversine_stdlib.c"
typedef struct Str8FindResult {
bool found;
HAV_Str8 match;
HAV_Str8 match_to_end_of_buffer;
} Str8FindResult;
static Str8FindResult FindFirstCharThatLooksLikeANumber(HAV_Str8 buffer)
{
Str8FindResult result = {0};
for (size_t index = 0; !result.found && index < buffer.size; index++) {
if (HAV_CharIsDigit(buffer.data[index]) || buffer.data[index] == '+' || buffer.data[index] == '-') {
result.found = true;
result.match = (HAV_Str8){.data = buffer.data + index, .size = 1};
result.match_to_end_of_buffer = (HAV_Str8){.data = result.match.data, .size = buffer.size - index};
break;
}
}
return result;
}
static f64 StringToF64(HAV_Str8 value)
{
f64 result = 0.f;
Str8FindResult find_result = FindFirstCharThatLooksLikeANumber(value);
if (!find_result.found)
return result;
bool negative = false;
HAV_Str8 real_number_string = find_result.match_to_end_of_buffer;
if (find_result.match.data[0] == '+' || find_result.match.data[0] == '-') {
negative = find_result.match.data[0] == '-';
real_number_string.data++;
real_number_string.size--;
}
HAV_Str8BinarySplitResult number_split = HAV_Str8_BinarySplit(real_number_string, HAV_STR8("."));
HAV_Str8 integer_part = number_split.lhs;
HAV_Str8 decimal_part = number_split.rhs;
uint64_t integer_part_as_u64 = 0;
for (size_t index = 0; index < integer_part.size; index++) {
integer_part_as_u64 *= 10;
integer_part_as_u64 += integer_part.data[index] - '0';
}
uint64_t decimal_part_as_u64 = 0;
uint64_t decimal_magnitude = 1;
for (size_t index = 0; index < decimal_part.size; index++) {
decimal_part_as_u64 *= 10;
decimal_part_as_u64 += decimal_part.data[index] - '0';
decimal_magnitude *= 10;
}
typedef union FU64 {
f64 f64_value;
uint64_t u64_value;
} FU64;
FU64 fu64 = {0};
fu64.f64_value = HAV_CAST(f64)integer_part_as_u64 + (HAV_CAST(f64)decimal_part_as_u64 / decimal_magnitude);
if (negative) {
fu64.u64_value |= (1ULL << 63);
}
return fu64.f64_value;
}
#define PRINT_USAGE HAV_PrintLnFmt("Usage: %s [haversine_input.json] [answers.f64]", argv[0])
int main(int argc, char **argv)
{
// NOTE: Arg Parsing
// =========================================================================
if (argc != 2 && argc != 3) {
PRINT_USAGE;
return -1;
}
g_profiler.begin_tsc = ReadCPUTimer();
HAV_Str8 arg_json = {argv[1], strlen(argv[1])};
HAV_Str8 arg_answers = {0};
if (argc == 3)
arg_answers = (HAV_Str8){.data = argv[2], .size = strlen(argv[2])};
HAV_Buffer json_buffer = HAV_FileRead(arg_json.data);
if (!HAV_BufferIsValid(json_buffer))
return 0;
HAV_ProfilerZone prof_parse_and_sum_zone = HAV_Profiler_BeginZone("Parse&Hav Sum");
f64 haversine_sum = 0;
size_t pair_count = 0;
HAV_Str8 json_it = (HAV_Str8){.data = json_buffer.data, .size = json_buffer.size};
for (;; pair_count++) {
f64 x0 = 0.f, y0 = 0.f, x1 = 0.f, y1 = 0.f;
HAV_ProfilerZone prof_json_parse_zone = HAV_Profiler_BeginZoneBandwidth("Parse", json_it.size);
HAV_Str8BinarySplitResult x0_key = HAV_Str8_BinarySplit(json_it, HAV_STR8("x0"));
if (x0_key.rhs.size) {
Str8FindResult x0_find_value = FindFirstCharThatLooksLikeANumber(x0_key.rhs);
HAV_Str8BinarySplitResult x0_value = HAV_Str8_BinarySplit(x0_find_value.match_to_end_of_buffer, HAV_STR8(","));
HAV_Str8BinarySplitResult y0_key = HAV_Str8_BinarySplit(x0_value.rhs, HAV_STR8("y0"));
Str8FindResult y0_find_value = FindFirstCharThatLooksLikeANumber(y0_key.rhs);
HAV_Str8BinarySplitResult y0_value = HAV_Str8_BinarySplit(y0_find_value.match_to_end_of_buffer, HAV_STR8(","));
HAV_Str8BinarySplitResult x1_key = HAV_Str8_BinarySplit(y0_value.rhs, HAV_STR8("x1"));
Str8FindResult x1_find_value = FindFirstCharThatLooksLikeANumber(x1_key.rhs);
HAV_Str8BinarySplitResult x1_value = HAV_Str8_BinarySplit(x1_find_value.match_to_end_of_buffer, HAV_STR8(","));
HAV_Str8BinarySplitResult y1_key = HAV_Str8_BinarySplit(x1_value.rhs, HAV_STR8("y1"));
Str8FindResult y1_find_value = FindFirstCharThatLooksLikeANumber(y1_key.rhs);
HAV_Str8BinarySplitResult y1_value = HAV_Str8_BinarySplit(y1_find_value.match_to_end_of_buffer, HAV_STR8("}"));
x0 = StringToF64(x0_value.lhs);
y0 = StringToF64(y0_value.lhs);
x1 = StringToF64(x1_value.lhs);
y1 = StringToF64(y1_value.lhs);
json_it = y1_value.rhs;
}
#if 0
HAV_PrintLnFmt("{x0: %.*s (%f), y0: %.*s (%f), x1 %.*s (%f), y1: %.*s (%f)}",
HAV_STR8_FMT(x0_value.lhs), x0,
HAV_STR8_FMT(y0_value.lhs), y0,
HAV_STR8_FMT(x1_value.lhs), x1,
HAV_STR8_FMT(y1_value.lhs), y1);
#endif
HAV_Profiler_EndZone(prof_json_parse_zone);
if (!x0_key.rhs.size)
break;
HAV_ProfilerZone prof_haversine_sum_zone = HAV_Profiler_BeginZoneBandwidth("Hav Sum", sizeof(x0) + sizeof(y0) + sizeof(x1) + sizeof(y1));
f64 haversine_dist = ReferenceHaversine(x0, y0, x1, y1, /*EarthRadius*/ 6372.8);
haversine_sum += haversine_dist;
HAV_Profiler_EndZone(prof_haversine_sum_zone);
}
HAV_Profiler_EndZone(prof_parse_and_sum_zone);
haversine_sum /= pair_count;
size_t input_size = json_buffer.size;
HAV_PrintLnFmt("Input size: %zu", input_size);
HAV_PrintLnFmt("Pair count: %zu", pair_count);
HAV_PrintLnFmt("Haversine sum: %f", haversine_sum);
if (arg_answers.size) {
HAV_Buffer answers_buffer = HAV_FileRead(arg_answers.data);
if (HAV_BufferIsValid(answers_buffer)) {
HAV_ASSERT(answers_buffer.size == (pair_count * sizeof(f64)) + /*Reference Sum*/ sizeof(f64));
f64 reference_haversine_sum = 0;
memcpy(&reference_haversine_sum, answers_buffer.data + (pair_count * sizeof(f64)), sizeof(reference_haversine_sum));
f64 difference = reference_haversine_sum - haversine_sum;
HAV_PrintLn(HAV_STR8("\nValidation: "));
HAV_PrintLnFmt("Reference sum: %f", reference_haversine_sum);
HAV_PrintLnFmt("Difference: %f", difference);
}
}
g_profiler.end_tsc = ReadCPUTimer();
HAV_Profiler_Dump();
return 0;
}
+171
View File
@@ -0,0 +1,171 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <Windows.h>
#include <math.h>
#include "haversine_stdlib.h"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "haversine_stdlib.c"
#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 ////////////////////////////////////////////////////////////////////////////
{
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("00"));
HAV_ASSERT(result.success);
HAV_ASSERT(result.value == 0);
}
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("+100"));
HAV_ASSERT(!result.success);
}
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("100,0"));
HAV_ASSERT(!result.success);
}
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("100a"));
HAV_ASSERT(!result.success);
}
{
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("3147"));
HAV_ASSERT(result.success);
HAV_ASSERT(result.value == 3147);
}
}
// NOTE: Arg Parsing ///////////////////////////////////////////////////////////////////////////
if (argc != 4) {
PRINT_USAGE;
return -1;
}
HAV_Str8 arg_uniform_cluster = {argv[1], strlen(argv[1])};
HAV_Str8 arg_random_seed = {argv[2], strlen(argv[2])};
HAV_Str8 arg_number_of_coordinate_pairs_to_generate = {argv[3], strlen(argv[3])};
typedef enum PointGenerator {
PointGenerator_Invalid,
PointGenerator_Uniform,
PointGenerator_Cluster,
} PointGenerator;
HAV_Str8ToU64Result random_seed_u64_result = HAV_Str8_ToU64(arg_random_seed);
HAV_Str8ToU64Result number_of_coordinate_pairs_to_generate_u64_result = HAV_Str8_ToU64(arg_number_of_coordinate_pairs_to_generate);
PointGenerator point_generator = PointGenerator_Invalid;
if (HAV_Str8_Equals(arg_uniform_cluster, HAV_STR8("uniform"))) {
point_generator = PointGenerator_Uniform;
} else if (HAV_Str8_Equals(arg_uniform_cluster, HAV_STR8("cluster"))) {
point_generator = PointGenerator_Cluster;
} else {
PRINT_USAGE;
return -1;
}
if (!random_seed_u64_result.success) {
HAV_PrintLnFmt("Random seed was not a valid U64 value [seed=%.*s]", HAV_STR8_FMT(arg_random_seed));
PRINT_USAGE;
return -1;
}
if (!number_of_coordinate_pairs_to_generate_u64_result.success) {
HAV_PrintLnFmt("Number of coordinate pairs to generate was not a valid U64 value [seed=%.*s]", HAV_STR8_FMT(arg_number_of_coordinate_pairs_to_generate));
PRINT_USAGE;
return -1;
}
uint32_t const MAX_COORD_PAIRS = 100'000'000;
if (number_of_coordinate_pairs_to_generate_u64_result.value > MAX_COORD_PAIRS) {
HAV_PrintLnFmt("Maximum number of coordinate pairs exceeded, exiting to avoid accidental large files [requested=%zu, max=%zu]",
number_of_coordinate_pairs_to_generate_u64_result.value,
MAX_COORD_PAIRS);
PRINT_USAGE;
return -1;
}
// 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;
char json_file_name[1024];
char answers_file_name[1024];
snprintf(json_file_name, sizeof(json_file_name), "haversine_%zu_%s_%zu.json", point_count, point_generator == PointGenerator_Cluster ? "cluster" : "uniform", random_seed);
snprintf(answers_file_name, sizeof(json_file_name), "haversine_%zu_%s_%zu.f64", point_count, point_generator == PointGenerator_Cluster ? "cluster" : "uniform", random_seed);
HANDLE haversine_json_file_handle = CreateFile(
/*LPCSTR lpFileName*/ json_file_name,
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
HANDLE haversine_f64_file_handle = CreateFile(
/*LPCSTR lpFileName*/ answers_file_name,
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
f64 const LAT_LON_MIN = -180.0;
f64 const LAT_LON_MAX = 180.0;
uint64_t const MAX_CLUSTERS = 64;
uint64_t const points_per_cluster = point_count / MAX_CLUSTERS;
char tmp_buffer[128];
f64 expected_sum = 0;
HAV_PrintHandle(haversine_json_file_handle, HAV_STR8("{\"pairs\":[\n"));
f64 const sum_coefficient = 1.0 / point_count;
f64 point_centre = 0;
f64 point_min_offset = LAT_LON_MIN;
f64 point_max_offset = LAT_LON_MAX;
for (int index = 0; index < point_count; index++) {
if (point_generator == PointGenerator_Cluster && (index % points_per_cluster) == 0) {
point_centre = HAV_PCG32_PieF64(&rng_state, LAT_LON_MIN, LAT_LON_MAX);
point_min_offset = -HAV_PCG32_PieF64(&rng_state, 0, 45);
point_max_offset = -point_min_offset;
}
f64 x0 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
f64 y0 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
f64 x1 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
f64 y1 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
f64 haversine_dist = ReferenceHaversine(x0, y0, x1, y1, /*EarthRadius*/ 6372.8);
HAV_PrintHandle(haversine_f64_file_handle, (HAV_Str8){.data = (char *)&haversine_dist, .size = sizeof(haversine_dist)});
expected_sum += (sum_coefficient * haversine_dist);
size_t json_line_size = snprintf(tmp_buffer, sizeof(tmp_buffer), " {\"x0\": %f, \"y0\": %f, \"x1\": %f, \"y1\": %f}%s\n", x0, y0, x1, y1, (index == (point_count - 1) ? "" : ","));
HAV_ASSERT(json_line_size < sizeof(tmp_buffer));
HAV_PrintHandle(haversine_json_file_handle, (HAV_Str8){.data = tmp_buffer, .size = json_line_size});
}
HAV_PrintHandle(haversine_json_file_handle, HAV_STR8("]}\n"));
HAV_PrintHandle(haversine_f64_file_handle, (HAV_Str8){.data = (char *)&expected_sum, .size = sizeof(expected_sum)});
CloseHandle(haversine_json_file_handle);
CloseHandle(haversine_f64_file_handle);
HAV_PrintLnFmt("Method: %s", (point_generator == PointGenerator_Uniform ? "uniform" : "cluster"));
HAV_PrintLnFmt("Seed: %zu", random_seed);
HAV_PrintLnFmt("Pair Count: %zu", point_count);
HAV_PrintLnFmt("Expected Sum: %f", expected_sum);
return 0;
}
+353
View File
@@ -0,0 +1,353 @@
// 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;
return result;
}
HAV_Str8ToU64Result HAV_Str8_ToU64(HAV_Str8 string)
{
HAV_Str8ToU64Result result = {0};
size_t ch_index = 0;
while (ch_index < string.size && HAV_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;
}
HAV_Str8BinarySplitResult HAV_Str8_BinarySplit(HAV_Str8 buffer, HAV_Str8 find)
{
HAV_Str8BinarySplitResult result = {0};
result.lhs = buffer;
for (size_t index = 0; (index + find.size) <= buffer.size; index++) {
HAV_Str8 check = {buffer.data + index, find.size};
if (HAV_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 HAV_CharIsWhiteSpace(char ch)
{
bool result = ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
return result;
}
bool HAV_CharIsDigit(char ch)
{
bool result = ch >= '0' && ch <= '9';
return result;
}
void HAV_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 < HAV_ARRAY_UCOUNT(g_profiler.anchors); index++) {
HAV_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%%", HAV_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 / HAV_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");
}
}
HAV_ProfilerZone HAV_Profiler_BeginZone_(HAV_Str8 label, uint32_t index, u64 byte_count)
{
HAV_ProfilerZone result = {0};
#if defined(HAV_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 HAV_Profiler_EndZone(HAV_ProfilerZone zone)
{
#if defined(HAV_PROFILER)
u64 elapsed_tsc = ReadCPUTimer() - zone.tsc;
HAV_ProfilerAnchor* anchor = g_profiler.anchors + zone.index;
HAV_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 HAV_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 HAV_PCG32_PieF64(uint64_t *state, f64 min, f64 max)
{
uint32_t u32_value = HAV_PCG32_Pie(state);
f64 t01 = HAV_CAST(f64)u32_value / HAV_CAST(f64)HAV_CAST(uint32_t)-1;
f64 result = min + (max - min) * t01;
return result;
}
bool HAV_BufferIsValid(HAV_Buffer buffer)
{
bool result = buffer.data && buffer.size;
return result;
}
HAV_BufferIterator HAV_BufferIteratorInit(HAV_Buffer buffer)
{
HAV_BufferIterator result = {0};
result.buffer = buffer;
return result;
}
bool HAV_BufferIteratorHasMoreBytes(HAV_BufferIterator it)
{
bool result = HAV_BufferIsValid(it.buffer) && it.index < it.buffer.size;
return result;
}
uint8_t HAV_BufferIteratorPeekByte(HAV_BufferIterator *it)
{
HAV_ASSERT(it);
HAV_ASSERT(HAV_BufferIsValid(it->buffer));
HAV_ASSERT(it->index < it->buffer.size);
uint8_t result = it->buffer.data[it->index];
return result;
}
uint8_t HAV_BufferIteratorNextByte(HAV_BufferIterator *it)
{
uint8_t result = HAV_BufferIteratorPeekByte(it);
it->index++;
return result;
}
HAV_Buffer HAV_FileRead(char const *file_path)
{
HAV_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;
HAV_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;
HAV_ProfilerZone prof_file_read_zone = HAV_Profiler_BeginZoneBandwidth("File Read", file_size);
BOOL read_file_result = ReadFile(
/*HANDLE hFile*/ file_handle,
/*LPVOID lpBuffer*/ buffer,
/*DWORD nNumberOfBytesToRead*/ HAV_CAST(DWORD)file_size,
/*LPDWORD lpNumberOfBytesRead*/ &bytes_read,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
HAV_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 HAV_FileFree(HAV_Buffer buffer)
{
if (HAV_BufferIsValid(buffer))
VirtualFree(buffer.data, 0, MEM_RELEASE);
}
bool HAV_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*/ HAV_CAST(DWORD)buffer_size,
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
HAV_ASSERT(bytes_written == buffer_size);
result = write_file_result && bytes_written == buffer_size;
CloseHandle(file_handle);
return result;
};
void HAV_PrintHandle(void *handle, HAV_Str8 string)
{
DWORD bytes_written = 0;
WriteFile(handle, string.data, HAV_CAST(DWORD)string.size, &bytes_written, NULL);
(void)bytes_written;
}
void HAV_Print(HAV_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;
}
HAV_ASSERT(string.size < HAV_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 {
HAV_PrintHandle(pap_globals.stdout_handle, string);
}
}
void HAV_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];
HAV_ASSERT(string_size >= 0 && string_size < HAV_ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
HAV_Str8 string = {.data = buffer, .size = string_size};
HAV_Print(string);
}
va_end(args);
}
void HAV_PrintLn(HAV_Str8 string)
{
HAV_Print(string);
HAV_Print(HAV_STR8("\n"));
}
void HAV_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];
HAV_ASSERT(string_size >= 0 && string_size < HAV_ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
HAV_Str8 string = {.data = buffer, .size = string_size};
HAV_PrintLn(string);
}
va_end(args);
}
+141
View File
@@ -0,0 +1,141 @@
#include <stdint.h>
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
#define HAV_STRINGIFY2(token) #token
#define HAV_STRINGIFY(token) HAV_STRINGIFY2(token)
#if defined(NDEBUG)
#define HAV_ASSERT(expr)
#else
#define HAV_ASSERT(expr) \
if (!(expr)) { \
HAV_PrintLnFmt("Assertion triggered [file=\"" __FILE__ ":" HAV_STRINGIFY(__LINE__) "\", expr=\"" #expr "\"]"); \
__debugbreak(); \
}
#endif
#define HAV_ARRAY_UCOUNT(array) sizeof((array)) / sizeof((array)[0])
#define HAV_CAST(Type) (Type)
#define HAV_MIN(a, b) ((a) < (b) ? (a) : (b))
#define HAV_MAX(a, b) ((a) > (b) ? (a) : (b))
typedef float f32;
typedef double f64;
typedef uint64_t u64;
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Globals {
HANDLE stdout_handle;
bool write_to_console;
} HAV_Globals;
HAV_Globals pap_globals;
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Str8 {
char *data;
size_t size;
} HAV_Str8;
typedef struct HAV_Str8ToU64Result {
bool success;
uint64_t value;
} HAV_Str8ToU64Result;
typedef struct HAV_Str8BinarySplitResult {
HAV_Str8 lhs;
HAV_Str8 rhs;
} HAV_Str8BinarySplitResult;
#define HAV_STR8(string) (HAV_Str8){.data = (string), .size = HAV_ARRAY_UCOUNT(string) - 1 }
#define HAV_STR8_FMT(string) (int)((string).size), (string).data
bool HAV_Str8_Equals(HAV_Str8 lhs, HAV_Str8 rhs);
HAV_Str8ToU64Result HAV_Str8_ToU64(HAV_Str8 string);
HAV_Str8BinarySplitResult HAV_Str8_BinarySplit(HAV_Str8 buffer, HAV_Str8 find);
bool HAV_CharIsWhiteSpace(char ch);
bool HAV_CharIsDigit(char ch);
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_ProfilerAnchor {
HAV_Str8 label;
u64 elapsed_tsc_exclusive; // Does not include children
u64 elapsed_tsc_inclusive; // Includes children
u64 byte_count;
u64 hits;
} HAV_ProfilerAnchor;
typedef struct HAV_Profiler {
HAV_ProfilerAnchor anchors[4096];
u64 begin_tsc;
u64 end_tsc;
u64 parent_index;
} HAV_Profiler;
typedef struct HAV_ProfilerZone {
u64 parent_index;
uint32_t index;
HAV_Str8 label;
u64 elapsed_tsc_inclusive;
u64 tsc;
u64 byte_count;
} HAV_ProfilerZone;
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 HAV_ProfilerZone HAV_Profiler_BeginZone_(HAV_Str8 label, uint32_t index, u64 byte_count);
static void HAV_Profiler_EndZone (HAV_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 HAV_PCG32_Pie (uint64_t *state);
f64 HAV_PCG32_PieF64(uint64_t *state, f64 min, f64 max);
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
typedef struct HAV_Buffer {
char *data;
size_t size;
} HAV_Buffer;
typedef struct HAV_BufferIterator {
HAV_Buffer buffer;
size_t index;
} HAV_BufferIterator;
bool HAV_BufferIsValid (HAV_Buffer buffer);
HAV_BufferIterator HAV_BufferIteratorInit (HAV_Buffer buffer);
bool HAV_BufferIteratorHasMoreBytes(HAV_BufferIterator it);
uint8_t HAV_BufferIteratorNextByte (HAV_BufferIterator *it);
// 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 /////////////////////////////////////////////////////////////////////////////////////
void HAV_PrintHandle(void *handle, HAV_Str8 string);
void HAV_PrintLn (HAV_Str8 string);
void HAV_PrintLnFmt (char const *fmt, ...);
+53
View File
@@ -0,0 +1,53 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 65
======================================================================== */
static f64 Square(f64 A)
{
f64 Result = (A*A);
return Result;
}
static f64 RadiansFromDegrees(f64 Degrees)
{
f64 Result = 0.01745329251994329577 * Degrees;
return Result;
}
// NOTE(casey): EarthRadius is generally expected to be 6372.8
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
{
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
question on which these homework exercises are loosely based.
*/
f64 lat1 = Y0;
f64 lat2 = Y1;
f64 lon1 = X0;
f64 lon2 = X1;
f64 dLat = RadiansFromDegrees(lat2 - lat1);
f64 dLon = RadiansFromDegrees(lon2 - lon1);
lat1 = RadiansFromDegrees(lat1);
lat2 = RadiansFromDegrees(lat2);
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
f64 c = 2.0*asin(sqrt(a));
f64 Result = EarthRadius * c;
return Result;
}
+220
View File
@@ -0,0 +1,220 @@
/* ========================================================================
(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 66
======================================================================== */
/* 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 <string.h>
typedef uint32_t u32;
typedef uint64_t u64;
typedef double f64;
#define U64Max UINT64_MAX
#include "listing_0065_haversine_formula.cpp"
struct random_series
{
u64 A, B, C, D;
};
static u64 RotateLeft(u64 V, int Shift)
{
u64 Result = ((V << Shift) | (V >> (64-Shift)));
return Result;
}
static u64 RandomU64(random_series *Series)
{
u64 A = Series->A;
u64 B = Series->B;
u64 C = Series->C;
u64 D = Series->D;
u64 E = A - RotateLeft(B, 27);
A = (B ^ RotateLeft(C, 17));
B = (C + D);
C = (D + E);
D = (E + A);
Series->A = A;
Series->B = B;
Series->C = C;
Series->D = D;
return D;
}
static random_series Seed(u64 Value)
{
random_series Series = {};
// NOTE(casey): This is the seed pattern for JSF generators, as per the original post
Series.A = 0xf1ea5eed;
Series.B = Value;
Series.C = Value;
Series.D = Value;
u32 Count = 20;
while(Count--)
{
RandomU64(&Series);
}
return Series;
}
static f64 RandomInRange(random_series *Series, f64 Min, f64 Max)
{
f64 t = (f64)RandomU64(Series) / (f64)U64Max;
f64 Result = (1.0 - t)*Min + t*Max;
return Result;
}
static FILE *Open(long long unsigned PairCount, char *Label, char *Extension)
{
char Temp[256];
sprintf(Temp, "data_%llu_%s.%s", PairCount, Label, Extension);
FILE *Result = fopen(Temp, "wb");
if(!Result)
{
fprintf(stderr, "Unable to open \"%s\" for writing.\n", Temp);
}
return Result;
}
static f64 RandomDegree(random_series *Series, f64 Center, f64 Radius, f64 MaxAllowed)
{
f64 MinVal = Center - Radius;
if(MinVal < -MaxAllowed)
{
MinVal = -MaxAllowed;
}
f64 MaxVal = Center + Radius;
if(MaxVal > MaxAllowed)
{
MaxVal = MaxAllowed;
}
f64 Result = RandomInRange(Series, MinVal, MaxVal);
return Result;
}
int main(int ArgCount, char **Args)
{
if(ArgCount == 4)
{
u64 ClusterCountLeft = U64Max;
f64 MaxAllowedX = 180;
f64 MaxAllowedY = 90;
f64 XCenter = 0;
f64 YCenter = 0;
f64 XRadius = MaxAllowedX;
f64 YRadius = MaxAllowedY;
char *MethodName = Args[1];
if(strcmp(MethodName, "cluster") == 0)
{
ClusterCountLeft = 0;
}
else if(strcmp(MethodName, "uniform") != 0)
{
MethodName = "uniform";
fprintf(stderr, "WARNING: Unrecognized method name. Using 'uniform'.\n");
}
u64 SeedValue = atoll(Args[2]);
random_series Series = Seed(SeedValue);
u64 MaxPairCount = (1ULL << 34);
u64 PairCount = atoll(Args[3]);
if(PairCount < MaxPairCount)
{
u64 ClusterCountMax = 1 + (PairCount / 64);
FILE *FlexJSON = Open(PairCount, "flex", "json");
FILE *HaverAnswers = Open(PairCount, "haveranswer", "f64");
if(FlexJSON && HaverAnswers)
{
fprintf(FlexJSON, "{\"pairs\":[\n");
f64 Sum = 0;
f64 SumCoef = 1.0 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
if(ClusterCountLeft-- == 0)
{
ClusterCountLeft = ClusterCountMax;
XCenter = RandomInRange(&Series, -MaxAllowedX, MaxAllowedX);
YCenter = RandomInRange(&Series, -MaxAllowedY, MaxAllowedY);
XRadius = RandomInRange(&Series, 0, MaxAllowedX);
YRadius = RandomInRange(&Series, 0, MaxAllowedY);
}
f64 X0 = RandomDegree(&Series, XCenter, XRadius, MaxAllowedX);
f64 Y0 = RandomDegree(&Series, YCenter, YRadius, MaxAllowedY);
f64 X1 = RandomDegree(&Series, XCenter, XRadius, MaxAllowedX);
f64 Y1 = RandomDegree(&Series, YCenter, YRadius, MaxAllowedY);
f64 EarthRadius = 6372.8;
f64 HaversineDistance = ReferenceHaversine(X0, Y0, X1, Y1, EarthRadius);
Sum += SumCoef*HaversineDistance;
char *JSONSep = (PairIndex == (PairCount - 1)) ? "\n" : ",\n";
fprintf(FlexJSON, " {\"x0\":%.16f, \"y0\":%.16f, \"x1\":%.16f, \"y1\":%.16f}%s", X0, Y0, X1, Y1, JSONSep);
fwrite(&HaversineDistance, sizeof(HaversineDistance), 1, HaverAnswers);
}
fprintf(FlexJSON, "]}\n");
fwrite(&Sum, sizeof(Sum), 1, HaverAnswers);
fprintf(stdout, "Method: %s\n", MethodName);
fprintf(stdout, "Random seed: %llu\n", SeedValue);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Expected sum: %.16f\n", Sum);
}
if(FlexJSON) fclose(FlexJSON);
if(HaverAnswers) fclose(HaverAnswers);
}
else
{
fprintf(stderr, "To avoid accidentally generating massive files, number of pairs must be less than %llu.\n", MaxPairCount);
}
}
else
{
fprintf(stderr, "Usage: %s [uniform/cluster] [random seed] [number of coordinate pairs to generate]\n", Args[0]);
}
return 0;
}
+165
View File
@@ -0,0 +1,165 @@
/* ========================================================================
(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 67
======================================================================== */
/* 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;
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0069_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
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);
}
}
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
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)
{
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);
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);
Result = 0;
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
return Result;
}
+72
View File
@@ -0,0 +1,72 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 68
======================================================================== */
struct buffer
{
size_t Count;
u8 *Data;
};
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
static b32 IsInBounds(buffer Source, u64 At)
{
b32 Result = (At < Source.Count);
return Result;
}
static b32 AreEqual(buffer A, buffer B)
{
if(A.Count != B.Count)
{
return false;
}
for(u64 Index = 0; Index < A.Count; ++Index)
{
if(A.Data[Index] != B.Data[Index])
{
return false;
}
}
return true;
}
static buffer AllocateBuffer(size_t Count)
{
buffer Result = {};
Result.Data = (u8 *)malloc(Count);
if(Result.Data)
{
Result.Count = Count;
}
else
{
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
}
return Result;
}
static void FreeBuffer(buffer *Buffer)
{
if(Buffer->Data)
{
free(Buffer->Data);
}
*Buffer = {};
}
+508
View File
@@ -0,0 +1,508 @@
/* ========================================================================
(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 69
======================================================================== */
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_semi_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 ';': {Result.Type = Token_semi_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 StartingToken, 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, Value, Token_close_bracket, false);
}
else if(Value.Type == Token_open_brace)
{
SubElement = ParseJSONList(Parser, Value, 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 StartingToken, 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)
{
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)
{
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;
}
+70
View File
@@ -0,0 +1,70 @@
/* ========================================================================
(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 70
======================================================================== */
#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();
}
+43
View File
@@ -0,0 +1,43 @@
/* ========================================================================
(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 71
======================================================================== */
#include <stdint.h>
#include <stdio.h>
typedef uint64_t u64;
typedef double f64;
#include "listing_0070_platform_metrics.cpp"
int main(void)
{
u64 OSFreq = GetOSTimerFreq();
printf(" OS Freq: %llu\n", OSFreq);
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
while(OSElapsed < OSFreq)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
/* ========================================================================
(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 72
======================================================================== */
#include <stdint.h>
#include <stdio.h>
typedef uint64_t u64;
typedef double f64;
#include "listing_0070_platform_metrics.cpp"
int main(void)
{
u64 OSFreq = GetOSTimerFreq();
printf(" OS Freq: %llu\n", OSFreq);
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
while(OSElapsed < OSFreq)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
printf(" CPU Timer: %llu -> %llu = %llu elapsed\n", CPUStart, CPUEnd, CPUElapsed);
return 0;
}
@@ -0,0 +1,63 @@
/* ========================================================================
(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 73
======================================================================== */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef uint64_t u64;
typedef double f64;
#include "listing_0070_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
u64 MillisecondsToWait = 1000;
if(ArgCount == 2)
{
MillisecondsToWait = atol(Args[1]);
}
u64 OSFreq = GetOSTimerFreq();
printf(" OS Freq: %llu (reported)\n", OSFreq);
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;
}
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
printf(" CPU Timer: %llu -> %llu = %llu elapsed\n", CPUStart, CPUEnd, CPUElapsed);
printf(" CPU Freq: %llu (guessed)\n", CPUFreq);
return 0;
}
+98
View File
@@ -0,0 +1,98 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 74
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
#endif
/* NOTE(casey): This does not need to be "inline", it could just be "static"
because compilers will inline it anyway. But compilers will warn about
static functions that aren't used. So "inline" is just the simplest way
to tell them to stop complaining about that. */
inline u64 ReadCPUTimer(void)
{
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
// with one of their performance counter read instructions, depending
// on which ones are available on your platform.
return __rdtsc();
}
static u64 EstimateCPUTimerFreq(void)
{
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
u64 CPUFreq = 0;
if(OSElapsed)
{
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
}
return CPUFreq;
}
+209
View File
@@ -0,0 +1,209 @@
/* ========================================================================
(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 75
======================================================================== */
/* 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;
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#include "listing_0074_platform_metrics.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0069_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
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);
}
}
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
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;
}
static void PrintTimeElapsed(char const *Label, u64 TotalTSCElapsed, u64 Begin, u64 End)
{
u64 Elapsed = End - Begin;
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
printf(" %s: %llu (%.2f%%)\n", Label, Elapsed, Percent);
}
int main(int ArgCount, char **Args)
{
u64 Prof_Begin = 0;
u64 Prof_Read = 0;
u64 Prof_MiscSetup = 0;
u64 Prof_Parse = 0;
u64 Prof_Sum = 0;
u64 Prof_MiscOutput = 0;
u64 Prof_End = 0;
Prof_Begin = ReadCPUTimer();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
Prof_Read = ReadCPUTimer();
buffer InputJSON = ReadEntireFile(Args[1]);
Prof_MiscSetup = ReadCPUTimer();
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;
Prof_Parse = ReadCPUTimer();
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
Prof_Sum = ReadCPUTimer();
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Prof_MiscOutput = ReadCPUTimer();
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]);
}
Prof_End = ReadCPUTimer();
if(Result == 0)
{
u64 TotalCPUElapsed = Prof_End - Prof_Begin;
u64 CPUFreq = EstimateCPUTimerFreq();
if(CPUFreq)
{
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
}
PrintTimeElapsed("Startup", TotalCPUElapsed, Prof_Begin, Prof_Read);
PrintTimeElapsed("Read", TotalCPUElapsed, Prof_Read, Prof_MiscSetup);
PrintTimeElapsed("MiscSetup", TotalCPUElapsed, Prof_MiscSetup, Prof_Parse);
PrintTimeElapsed("Parse", TotalCPUElapsed, Prof_Parse, Prof_Sum);
PrintTimeElapsed("Sum", TotalCPUElapsed, Prof_Sum, Prof_MiscOutput);
PrintTimeElapsed("MiscOutput", TotalCPUElapsed, Prof_MiscOutput, Prof_End);
}
return Result;
}
+101
View File
@@ -0,0 +1,101 @@
/* ========================================================================
(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 76
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
struct profile_anchor
{
u64 TSCElapsed;
u64 HitCount;
char const *Label;
};
struct profiler
{
profile_anchor Anchors[4096];
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
AnchorIndex = AnchorIndex_;
Label = Label_;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - StartTSC;
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
Anchor->TSCElapsed += 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 StartTSC;
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 TimeFunction TimeBlock(__func__)
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
{
u64 Elapsed = Anchor->TSCElapsed;
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%)\n", Anchor->Label, Anchor->HitCount, Elapsed, Percent);
}
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);
}
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
if(Anchor->TSCElapsed)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
@@ -0,0 +1,509 @@
/* ========================================================================
(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 77
======================================================================== */
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)
{
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)
{
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,182 @@
/* ========================================================================
(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 78
======================================================================== */
/* 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_0076_simple_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0077_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);
}
}
}
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,510 @@
/* ========================================================================
(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 79
======================================================================== */
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)
{
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"));
}
}
FreeJSON(JSON);
return PairCount;
}
@@ -0,0 +1,182 @@
/* ========================================================================
(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 80
======================================================================== */
/* 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_0076_simple_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0079_timedblock_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);
}
}
}
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");
+118
View File
@@ -0,0 +1,118 @@
/* ========================================================================
(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 81
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
struct profile_anchor
{
u64 TSCElapsed;
u64 TSCElapsedChildren;
u64 HitCount;
char const *Label;
};
struct profiler
{
profile_anchor Anchors[4096];
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
GlobalProfilerParent = AnchorIndex;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
Parent->TSCElapsedChildren += Elapsed;
Anchor->TSCElapsed += 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 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 TimeFunction TimeBlock(__func__)
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
{
u64 Elapsed = Anchor->TSCElapsed - Anchor->TSCElapsedChildren;
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Elapsed, Percent);
if(Anchor->TSCElapsedChildren)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsed / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")\n");
}
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);
}
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
if(Anchor->TSCElapsed)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
@@ -0,0 +1,182 @@
/* ========================================================================
(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 82
======================================================================== */
/* 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_0081_nesting_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0079_timedblock_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);
}
}
}
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,512 @@
/* ========================================================================
(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 83
======================================================================== */
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)
{
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)
{
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)
{
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"));
}
}
FreeJSON(JSON);
return PairCount;
}
@@ -0,0 +1,182 @@
/* ========================================================================
(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 84
======================================================================== */
/* 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_0081_nesting_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);
}
}
}
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");
+125
View File
@@ -0,0 +1,125 @@
/* ========================================================================
(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 85
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
struct profile_anchor
{
u64 TSCElapsed;
u64 TSCElapsedChildren;
u64 TSCElapsedAtRoot;
u64 HitCount;
char const *Label;
};
struct profiler
{
profile_anchor Anchors[4096];
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
OldTSCElapsedAtRoot = Anchor->TSCElapsedAtRoot;
GlobalProfilerParent = AnchorIndex;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
Parent->TSCElapsedChildren += Elapsed;
Anchor->TSCElapsedAtRoot = OldTSCElapsedAtRoot + Elapsed;
Anchor->TSCElapsed += 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 OldTSCElapsedAtRoot;
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 TimeFunction TimeBlock(__func__)
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
{
u64 TSCElapsedSelf = Anchor->TSCElapsed - Anchor->TSCElapsedChildren;
f64 Percent = 100.0 * ((f64)TSCElapsedSelf / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, TSCElapsedSelf, Percent);
if(Anchor->TSCElapsedAtRoot != TSCElapsedSelf)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedAtRoot / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")\n");
}
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);
}
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
if(Anchor->TSCElapsed)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
@@ -0,0 +1,182 @@
/* ========================================================================
(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 86
======================================================================== */
/* 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_0085_recursive_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);
}
}
}
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");
+123
View File
@@ -0,0 +1,123 @@
/* ========================================================================
(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 87
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
char const *Label;
};
struct profiler
{
profile_anchor Anchors[4096];
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
GlobalProfilerParent = AnchorIndex;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
profile_anchor *Anchor = GlobalProfiler.Anchors + 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 TimeFunction TimeBlock(__func__)
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 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);
}
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
@@ -0,0 +1,182 @@
/* ========================================================================
(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 88
======================================================================== */
/* 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_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);
}
}
}
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,537 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 89
======================================================================== */
enum json_token_type
{
Token_end_of_stream,
Token_error,
Token_open_brace,
Token_open_bracket,
Token_close_brace,
Token_close_bracket,
Token_comma,
Token_colon,
Token_string_literal,
Token_number,
Token_true,
Token_false,
Token_null,
Token_count,
};
struct json_token
{
json_token_type Type;
buffer Value;
};
struct json_element
{
buffer Label;
buffer Value;
json_element *FirstSubElement;
json_element *NextSibling;
};
struct json_parser
{
buffer Source;
u64 At;
b32 HadError;
};
static b32 IsJSONDigit(buffer Source, u64 At)
{
TimeFunction;
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val >= '0') && (Val <= '9'));
}
return Result;
}
static b32 IsJSONWhitespace(buffer Source, u64 At)
{
TimeFunction;
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
}
return Result;
}
static b32 IsParsing(json_parser *Parser)
{
TimeFunction;
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
return Result;
}
static void Error(json_parser *Parser, json_token Token, char const *Message)
{
TimeFunction;
Parser->HadError = true;
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
}
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
{
TimeFunction;
if((Source.Count - *At) >= KeywordRemaining.Count)
{
buffer Check = Source;
Check.Data += *At;
Check.Count = KeywordRemaining.Count;
if(AreEqual(Check, KeywordRemaining))
{
Result->Type = Type;
Result->Value.Count += KeywordRemaining.Count;
*At += KeywordRemaining.Count;
}
}
}
static json_token GetJSONToken(json_parser *Parser)
{
TimeFunction;
json_token Result = {};
buffer Source = Parser->Source;
u64 At = Parser->At;
while(IsJSONWhitespace(Source, At))
{
++At;
}
if(IsInBounds(Source, At))
{
Result.Type = Token_error;
Result.Value.Count = 1;
Result.Value.Data = Source.Data + At;
u8 Val = Source.Data[At++];
switch(Val)
{
case '{': {Result.Type = Token_open_brace;} break;
case '[': {Result.Type = Token_open_bracket;} break;
case '}': {Result.Type = Token_close_brace;} break;
case ']': {Result.Type = Token_close_bracket;} break;
case ',': {Result.Type = Token_comma;} break;
case ':': {Result.Type = Token_colon;} break;
case 'f':
{
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
} break;
case 'n':
{
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
} break;
case 't':
{
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
} break;
case '"':
{
Result.Type = Token_string_literal;
u64 StringStart = At;
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
{
if(IsInBounds(Source, (At + 1)) &&
(Source.Data[At] == '\\') &&
(Source.Data[At + 1] == '"'))
{
// NOTE(casey): Skip escaped quotation marks
++At;
}
++At;
}
Result.Value.Data = Source.Data + StringStart;
Result.Value.Count = At - StringStart;
if(IsInBounds(Source, At))
{
++At;
}
} break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
u64 Start = At - 1;
Result.Type = Token_number;
// NOTE(casey): Move past a leading negative sign if one exists
if((Val == '-') && IsInBounds(Source, At))
{
Val = Source.Data[At++];
}
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
if(Val != '0')
{
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
{
++At;
}
while(IsJSONDigit(Source, At))
{
++At;
}
}
Result.Value.Count = At - Start;
} break;
default:
{
} break;
}
}
Parser->At = At;
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
{
TimeFunction;
b32 Valid = true;
json_element *SubElement = 0;
if(Value.Type == Token_open_bracket)
{
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
}
else if(Value.Type == Token_open_brace)
{
SubElement = ParseJSONList(Parser, Token_close_brace, true);
}
else if((Value.Type == Token_string_literal) ||
(Value.Type == Token_true) ||
(Value.Type == Token_false) ||
(Value.Type == Token_null) ||
(Value.Type == Token_number))
{
// NOTE(casey): Nothing to do here, since there is no additional data
}
else
{
Valid = false;
}
json_element *Result = 0;
if(Valid)
{
Result = (json_element *)malloc(sizeof(json_element));
Result->Label = Label;
Result->Value = Value.Value;
Result->FirstSubElement = SubElement;
Result->NextSibling = 0;
}
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
{
TimeFunction;
json_element *FirstElement = {};
json_element *LastElement = {};
while(IsParsing(Parser))
{
buffer Label = {};
json_token Value = GetJSONToken(Parser);
if(HasLabels)
{
if(Value.Type == Token_string_literal)
{
Label = Value.Value;
json_token Colon = GetJSONToken(Parser);
if(Colon.Type == Token_colon)
{
Value = GetJSONToken(Parser);
}
else
{
Error(Parser, Colon, "Expected colon after field name");
}
}
else if(Value.Type != EndType)
{
Error(Parser, Value, "Unexpected token in JSON");
}
}
json_element *Element = ParseJSONElement(Parser, Label, Value);
if(Element)
{
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
}
else if(Value.Type == EndType)
{
break;
}
else
{
Error(Parser, Value, "Unexpected token in JSON");
}
json_token Comma = GetJSONToken(Parser);
if(Comma.Type == EndType)
{
break;
}
else if(Comma.Type != Token_comma)
{
Error(Parser, Comma, "Unexpected token in JSON");
}
}
return FirstElement;
}
static json_element *ParseJSON(buffer InputJSON)
{
TimeFunction;
json_parser Parser = {};
Parser.Source = InputJSON;
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
return Result;
}
static void FreeJSON(json_element *Element)
{
TimeFunction;
while(Element)
{
json_element *FreeElement = Element;
Element = Element->NextSibling;
FreeJSON(FreeElement->FirstSubElement);
free(FreeElement);
}
}
static json_element *LookupElement(json_element *Object, buffer ElementName)
{
TimeFunction;
json_element *Result = 0;
if(Object)
{
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
{
if(AreEqual(Search->Label, ElementName))
{
Result = Search;
break;
}
}
}
return Result;
}
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
{
TimeFunction;
u64 At = *AtResult;
f64 Result = 1.0;
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
{
Result = -1.0;
++At;
}
*AtResult = At;
return Result;
}
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
{
TimeFunction;
u64 At = *AtResult;
f64 Result = 0.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Result = 10.0*Result + (f64)Char;
++At;
}
else
{
break;
}
}
*AtResult = At;
return Result;
}
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
{
TimeFunction;
f64 Result = 0.0;
json_element *Element = LookupElement(Object, ElementName);
if(Element)
{
buffer Source = Element->Value;
u64 At = 0;
f64 Sign = ConvertJSONSign(Source, &At);
f64 Number = ConvertJSONNumber(Source, &At);
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
f64 C = 1.0 / 10.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Number = Number + C*(f64)Char;
C *= 1.0 / 10.0;
++At;
}
else
{
break;
}
}
}
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
{
++At;
}
f64 ExponentSign = ConvertJSONSign(Source, &At);
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
Number *= pow(10.0, Exponent);
}
Result = Sign*Number;
}
return Result;
}
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
{
TimeFunction;
u64 PairCount = 0;
json_element *JSON = ParseJSON(InputJSON);
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
if(PairsArray)
{
for(json_element *Element = PairsArray->FirstSubElement;
Element && (PairCount < MaxPairCount);
Element = Element->NextSibling)
{
haversine_pair *Pair = Pairs + PairCount++;
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
}
}
FreeJSON(JSON);
return PairCount;
}
@@ -0,0 +1,184 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 90
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#include "listing_0087_simplified_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
+142
View File
@@ -0,0 +1,142 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 91
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
GlobalProfilerParent = AnchorIndex;
StartTSC = ReadCPUTimer();
}
~profile_block(void)
{
u64 Elapsed = ReadCPUTimer() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
Parent->TSCElapsedExclusive -= Elapsed;
Anchor->TSCElapsedExclusive += Elapsed;
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
++Anchor->HitCount;
/* NOTE(casey): This write happens every time solely because there is no
straightforward way in C++ to have the same ease-of-use. In a better programming
language, it would be simple to have the anchor points gathered and labeled at compile
time, and this repetative write would be eliminated. */
Anchor->Label = Label;
}
char const *Label;
u64 OldTSCElapsedInclusive;
u64 StartTSC;
u32 ParentIndex;
u32 AnchorIndex;
};
#define NameConcat2(A, B) A##B
#define NameConcat(A, B) NameConcat2(A, B)
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
{
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
#else
#define TimeBlock(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeFunction TimeBlock(__func__)
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = ReadCPUTimer();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = ReadCPUTimer();
u64 CPUFreq = EstimateCPUTimerFreq();
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(CPUFreq)
{
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
}
PrintAnchorData(TotalCPUElapsed);
}
@@ -0,0 +1,185 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 92
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
@@ -0,0 +1,184 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 93
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#include "listing_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
@@ -0,0 +1,516 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 94
======================================================================== */
enum json_token_type
{
Token_end_of_stream,
Token_error,
Token_open_brace,
Token_open_bracket,
Token_close_brace,
Token_close_bracket,
Token_comma,
Token_colon,
Token_string_literal,
Token_number,
Token_true,
Token_false,
Token_null,
Token_count,
};
struct json_token
{
json_token_type Type;
buffer Value;
};
struct json_element
{
buffer Label;
buffer Value;
json_element *FirstSubElement;
json_element *NextSibling;
};
struct json_parser
{
buffer Source;
u64 At;
b32 HadError;
};
static b32 IsJSONDigit(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val >= '0') && (Val <= '9'));
}
return Result;
}
static b32 IsJSONWhitespace(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
}
return Result;
}
static b32 IsParsing(json_parser *Parser)
{
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
return Result;
}
static void Error(json_parser *Parser, json_token Token, char const *Message)
{
Parser->HadError = true;
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
}
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
{
if((Source.Count - *At) >= KeywordRemaining.Count)
{
buffer Check = Source;
Check.Data += *At;
Check.Count = KeywordRemaining.Count;
if(AreEqual(Check, KeywordRemaining))
{
Result->Type = Type;
Result->Value.Count += KeywordRemaining.Count;
*At += KeywordRemaining.Count;
}
}
}
static json_token GetJSONToken(json_parser *Parser)
{
json_token Result = {};
buffer Source = Parser->Source;
u64 At = Parser->At;
while(IsJSONWhitespace(Source, At))
{
++At;
}
if(IsInBounds(Source, At))
{
Result.Type = Token_error;
Result.Value.Count = 1;
Result.Value.Data = Source.Data + At;
u8 Val = Source.Data[At++];
switch(Val)
{
case '{': {Result.Type = Token_open_brace;} break;
case '[': {Result.Type = Token_open_bracket;} break;
case '}': {Result.Type = Token_close_brace;} break;
case ']': {Result.Type = Token_close_bracket;} break;
case ',': {Result.Type = Token_comma;} break;
case ':': {Result.Type = Token_colon;} break;
case 'f':
{
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
} break;
case 'n':
{
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
} break;
case 't':
{
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
} break;
case '"':
{
Result.Type = Token_string_literal;
u64 StringStart = At;
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
{
if(IsInBounds(Source, (At + 1)) &&
(Source.Data[At] == '\\') &&
(Source.Data[At + 1] == '"'))
{
// NOTE(casey): Skip escaped quotation marks
++At;
}
++At;
}
Result.Value.Data = Source.Data + StringStart;
Result.Value.Count = At - StringStart;
if(IsInBounds(Source, At))
{
++At;
}
} break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
u64 Start = At - 1;
Result.Type = Token_number;
// NOTE(casey): Move past a leading negative sign if one exists
if((Val == '-') && IsInBounds(Source, At))
{
Val = Source.Data[At++];
}
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
if(Val != '0')
{
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
{
++At;
}
while(IsJSONDigit(Source, At))
{
++At;
}
}
Result.Value.Count = At - Start;
} break;
default:
{
} break;
}
}
Parser->At = At;
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
{
b32 Valid = true;
json_element *SubElement = 0;
if(Value.Type == Token_open_bracket)
{
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
}
else if(Value.Type == Token_open_brace)
{
SubElement = ParseJSONList(Parser, Token_close_brace, true);
}
else if((Value.Type == Token_string_literal) ||
(Value.Type == Token_true) ||
(Value.Type == Token_false) ||
(Value.Type == Token_null) ||
(Value.Type == Token_number))
{
// NOTE(casey): Nothing to do here, since there is no additional data
}
else
{
Valid = false;
}
json_element *Result = 0;
if(Valid)
{
Result = (json_element *)malloc(sizeof(json_element));
Result->Label = Label;
Result->Value = Value.Value;
Result->FirstSubElement = SubElement;
Result->NextSibling = 0;
}
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
{
json_element *FirstElement = {};
json_element *LastElement = {};
while(IsParsing(Parser))
{
buffer Label = {};
json_token Value = GetJSONToken(Parser);
if(HasLabels)
{
if(Value.Type == Token_string_literal)
{
Label = Value.Value;
json_token Colon = GetJSONToken(Parser);
if(Colon.Type == Token_colon)
{
Value = GetJSONToken(Parser);
}
else
{
Error(Parser, Colon, "Expected colon after field name");
}
}
else if(Value.Type != EndType)
{
Error(Parser, Value, "Unexpected token in JSON");
}
}
json_element *Element = ParseJSONElement(Parser, Label, Value);
if(Element)
{
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
}
else if(Value.Type == EndType)
{
break;
}
else
{
Error(Parser, Value, "Unexpected token in JSON");
}
json_token Comma = GetJSONToken(Parser);
if(Comma.Type == EndType)
{
break;
}
else if(Comma.Type != Token_comma)
{
Error(Parser, Comma, "Unexpected token in JSON");
}
}
return FirstElement;
}
static json_element *ParseJSON(buffer InputJSON)
{
TimeFunction;
json_parser Parser = {};
Parser.Source = InputJSON;
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
return Result;
}
static void FreeJSON(json_element *Element)
{
while(Element)
{
json_element *FreeElement = Element;
Element = Element->NextSibling;
FreeJSON(FreeElement->FirstSubElement);
free(FreeElement);
}
}
static json_element *LookupElement(json_element *Object, buffer ElementName)
{
json_element *Result = 0;
if(Object)
{
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
{
if(AreEqual(Search->Label, ElementName))
{
Result = Search;
break;
}
}
}
return Result;
}
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 1.0;
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
{
Result = -1.0;
++At;
}
*AtResult = At;
return Result;
}
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 0.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Result = 10.0*Result + (f64)Char;
++At;
}
else
{
break;
}
}
*AtResult = At;
return Result;
}
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
{
f64 Result = 0.0;
json_element *Element = LookupElement(Object, ElementName);
if(Element)
{
buffer Source = Element->Value;
u64 At = 0;
f64 Sign = ConvertJSONSign(Source, &At);
f64 Number = ConvertJSONNumber(Source, &At);
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
f64 C = 1.0 / 10.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Number = Number + C*(f64)Char;
C *= 1.0 / 10.0;
++At;
}
else
{
break;
}
}
}
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
{
++At;
}
f64 ExponentSign = ConvertJSONSign(Source, &At);
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
Number *= pow(10.0, Exponent);
}
Result = Sign*Number;
}
return Result;
}
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
{
TimeFunction;
u64 PairCount = 0;
json_element *JSON = ParseJSON(InputJSON);
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
if(PairsArray)
{
TimeBlock("Lookup and Convert");
for(json_element *Element = PairsArray->FirstSubElement;
Element && (PairCount < MaxPairCount);
Element = Element->NextSibling)
{
haversine_pair *Pair = Pairs + PairCount++;
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
}
}
{
TimeBlock("FreeJSON");
FreeJSON(JSON);
}
return PairCount;
}
@@ -0,0 +1,185 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 95
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0094_profiled_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
+176
View File
@@ -0,0 +1,176 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 96
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#ifndef READ_BLOCK_TIMER
#define READ_BLOCK_TIMER ReadCPUTimer
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
GlobalProfilerParent = AnchorIndex;
StartTSC = READ_BLOCK_TIMER();
}
~profile_block(void)
{
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
Parent->TSCElapsedExclusive -= Elapsed;
Anchor->TSCElapsedExclusive += Elapsed;
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
++Anchor->HitCount;
/* NOTE(casey): This write happens every time solely because there is no
straightforward way in C++ to have the same ease-of-use. In a better programming
language, it would be simple to have the anchor points gathered and labeled at compile
time, and this repetative write would be eliminated. */
Anchor->Label = Label;
}
char const *Label;
u64 OldTSCElapsedInclusive;
u64 StartTSC;
u32 ParentIndex;
u32 AnchorIndex;
};
#define NameConcat2(A, B) A##B
#define NameConcat(A, B) NameConcat2(A, B)
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
{
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, Anchor);
}
}
}
#else
#define TimeBlock(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeFunction TimeBlock(__func__)
static u64 EstimateBlockTimerFreq(void)
{
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 BlockStart = READ_BLOCK_TIMER();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 BlockEnd = READ_BLOCK_TIMER();
u64 BlockElapsed = BlockEnd - BlockStart;
u64 BlockFreq = 0;
if(OSElapsed)
{
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
}
return BlockFreq;
}
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
u64 TimerFreq = EstimateBlockTimerFreq();
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(TimerFreq)
{
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
}
PrintAnchorData(TotalTSCElapsed);
}
+185
View File
@@ -0,0 +1,185 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 97
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0096_selectable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
+186
View File
@@ -0,0 +1,186 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 98
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#define READ_BLOCK_TIMER ReadOSTimer
#include "listing_0096_selectable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
@@ -0,0 +1,186 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 99
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#define READ_BLOCK_TIMER ReadOSTimer
#include "listing_0091_switchable_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0094_profiled_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeFunction;
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
+353
View File
@@ -0,0 +1,353 @@
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
bool Str8_Equals(Str8 lhs, Str8 rhs)
{
bool result = lhs.size == rhs.size && memcmp(lhs.data, rhs.data, lhs.size) == 0;
return result;
}
Str8ToU64Result Str8_ToU64(Str8 string)
{
Str8ToU64Result result = {0};
size_t ch_index = 0;
while (ch_index < string.size && CharIsWhiteSpace(string.data[ch_index]))
ch_index++;
for (; ch_index < string.size; ch_index++) {
char ch = string.data[ch_index];
if (ch >= '0' && ch <= '9') {
result.value = (result.value * 10) + (ch - '0');
} else {
return result;
}
}
result.success = true;
return result;
}
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find)
{
Str8BinarySplitResult result = {0};
result.lhs = buffer;
for (size_t index = 0; (index + find.size) <= buffer.size; index++) {
Str8 check = {buffer.data + index, find.size};
if (Str8_Equals(find, check)) {
result.lhs.size = index;
result.rhs.data = check.data + find.size;
result.rhs.size = buffer.size - (index + find.size);
break;
}
}
return result;
}
bool CharIsWhiteSpace(char ch)
{
bool result = ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
return result;
}
bool CharIsDigit(char ch)
{
bool result = ch >= '0' && ch <= '9';
return result;
}
void Profiler_Dump()
{
u64 total_elapsed_tsc = g_profiler.end_tsc - g_profiler.begin_tsc;
u64 cpu_frequency = EstimateCPUTimerFreq();
if (cpu_frequency)
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)total_elapsed_tsc / (f64)cpu_frequency, cpu_frequency);
for (uint32_t index = 1; index < ARRAY_UCOUNT(g_profiler.anchors); index++) {
ProfilerAnchor const *anchor = g_profiler.anchors + index;
if (!anchor->elapsed_tsc_inclusive)
break;
f64 percent = total_elapsed_tsc ? (f64)anchor->elapsed_tsc_exclusive / (f64)total_elapsed_tsc * 100.0 : 100.0;
printf(" %.*s[%zu]: %llu (%.2f%%", STR8_FMT(anchor->label), anchor->hits, anchor->elapsed_tsc_exclusive, percent);
if (anchor->elapsed_tsc_inclusive != anchor->elapsed_tsc_exclusive) {
f64 percent_w_children = total_elapsed_tsc ? ((f64)anchor->elapsed_tsc_inclusive / (f64)total_elapsed_tsc * 100.0) : 100.0;
printf(", %.2f%% w/children", percent_w_children);
}
printf(")");
if (anchor->byte_count) {
f64 megabytes_processed = anchor->byte_count / (1024.f * 1024.f);
f64 elapsed_s = anchor->elapsed_tsc_inclusive / CAST(f64)cpu_frequency;
f64 bytes_per_s = anchor->byte_count / elapsed_s;
f64 gigabytes_bandwidth = bytes_per_s / (1024.f * 1024.f * 1024.f);
printf(" %.3fmb at %.2fgb/s", megabytes_processed, gigabytes_bandwidth);
}
printf("\n");
}
}
ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count)
{
ProfilerZone result = {0};
#if defined(PROFILER)
result.index = index;
result.label = label;
result.tsc = ReadCPUTimer();
result.elapsed_tsc_inclusive = g_profiler.anchors[index].elapsed_tsc_inclusive;
result.byte_count = byte_count;
result.parent_index = g_profiler.parent_index;
g_profiler.parent_index = index;
#else
(void)label; (void)index; (void)byte_count;
#endif
return result;
}
void Profiler_EndZone(ProfilerZone zone)
{
#if defined(PROFILER)
u64 elapsed_tsc = ReadCPUTimer() - zone.tsc;
ProfilerAnchor* anchor = g_profiler.anchors + zone.index;
ProfilerAnchor* parent = g_profiler.anchors + zone.parent_index;
anchor->elapsed_tsc_exclusive += elapsed_tsc;
anchor->elapsed_tsc_inclusive = zone.elapsed_tsc_inclusive + elapsed_tsc;
anchor->label = zone.label;
anchor->byte_count += zone.byte_count;
anchor->hits++;
parent->elapsed_tsc_exclusive -= elapsed_tsc;
g_profiler.parent_index = zone.parent_index;
#else
(void)zone;
#endif
}
#pragma warning(push)
#pragma warning(disable: 4146) // warning C4146: unary minus operator applied to unsigned type, result still unsigned
uint32_t PCG32_Pie (uint64_t *state)
{
uint64_t old = *state ^ 0xc90fdaa2adf85459ULL;
*state = *state * 6364136223846793005ULL + 0xc90fdaa2adf85459ULL;
uint32_t xorshifted = (uint32_t)(((old >> 18u) ^ old) >> 27u);
uint32_t rot = old >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
#pragma warning(pop)
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max)
{
uint32_t u32_value = PCG32_Pie(state);
f64 t01 = CAST(f64)u32_value / CAST(f64)CAST(uint32_t)-1;
f64 result = min + (max - min) * t01;
return result;
}
bool BufferIsValid(Buffer buffer)
{
bool result = buffer.data && buffer.size;
return result;
}
BufferIterator BufferIteratorInit(Buffer buffer)
{
BufferIterator result = {0};
result.buffer = buffer;
return result;
}
bool BufferIteratorHasMoreBytes(BufferIterator it)
{
bool result = BufferIsValid(it.buffer) && it.index < it.buffer.size;
return result;
}
uint8_t BufferIteratorPeekByte(BufferIterator *it)
{
ASSERT(it);
ASSERT(BufferIsValid(it->buffer));
ASSERT(it->index < it->buffer.size);
uint8_t result = it->buffer.data[it->index];
return result;
}
uint8_t BufferIteratorNextByte(BufferIterator *it)
{
uint8_t result = BufferIteratorPeekByte(it);
it->index++;
return result;
}
Buffer FileRead(char const *file_path)
{
Buffer result = {0};
// NOTE: Determine file size ///////////////////////////////////////////////////////////////////
WIN32_FILE_ATTRIBUTE_DATA file_attrib_data = {0};
if (GetFileAttributesEx(file_path, GetFileExInfoStandard, &file_attrib_data) == 0)
return result;
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_READ,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ OPEN_EXISTING,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Allocate buffer ///////////////////////////////////////////////////////////////////////
uint64_t file_size = (uint64_t)file_attrib_data.nFileSizeHigh << 32 | (uint64_t)file_attrib_data.nFileSizeLow << 0;
ASSERT(file_size < (DWORD)-1);
char *buffer = VirtualAlloc(
/*LPVOID lpAddress*/ NULL,
/*SIZE_T dwSize*/ file_size,
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
/*DWORD flProtect*/ PAGE_READWRITE
);
if (!buffer)
goto end;
// NOTE: Read file to buffer ///////////////////////////////////////////////////////////////////
DWORD bytes_read = 0;
ProfilerZone prof_file_read_zone = Profiler_BeginZoneBandwidth("File Read", file_size);
BOOL read_file_result = ReadFile(
/*HANDLE hFile*/ file_handle,
/*LPVOID lpBuffer*/ buffer,
/*DWORD nNumberOfBytesToRead*/ CAST(DWORD)file_size,
/*LPDWORD lpNumberOfBytesRead*/ &bytes_read,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
Profiler_EndZone(prof_file_read_zone);
// NOTE: Handle read result ////////////////////////////////////////////////////////////////////
if (read_file_result == 0) {
VirtualFree(buffer, 0, MEM_RELEASE);
} else {
result.data = buffer;
result.size = file_size;
}
end:
CloseHandle(file_handle);
return result;
};
void FileFree(Buffer buffer)
{
if (BufferIsValid(buffer))
VirtualFree(buffer.data, 0, MEM_RELEASE);
}
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size)
{
bool result = false;
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
HANDLE file_handle = CreateFile(
/*LPCSTR lpFileName*/ file_path,
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
/*DWORD dwShareMode*/ 0,
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
/*DWORD dwFlagsAndAttributes*/ 0,
/*HANDLE hTemplateFile*/ NULL
);
if (file_handle == INVALID_HANDLE_VALUE)
return result;
// NOTE: Write file to disk ////////////////////////////////////////////////////////////////////
DWORD bytes_written = 0;
BOOL write_file_result = WriteFile(
/*HANDLE hFile*/ file_handle,
/*LPVOID lpBuffer*/ buffer,
/*DWORD nNumberOfBytesToWrite*/ CAST(DWORD)buffer_size,
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
/*LPOVERLAPPED lpOverlapped*/ NULL
);
ASSERT(bytes_written == buffer_size);
result = write_file_result && bytes_written == buffer_size;
CloseHandle(file_handle);
return result;
};
void PrintHandle(void *handle, Str8 string)
{
DWORD bytes_written = 0;
WriteFile(handle, string.data, CAST(DWORD)string.size, &bytes_written, NULL);
(void)bytes_written;
}
void Print(Str8 string)
{
if (pap_globals.stdout_handle == NULL) {
pap_globals.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
BOOL get_console_mode_result = GetConsoleMode(
/*HANDLE hConsoleHandle*/ pap_globals.stdout_handle,
/*LPDWORD lpMode*/ &mode
);
pap_globals.write_to_console = get_console_mode_result != 0;
}
ASSERT(string.size < CAST(DWORD)-1);
if (pap_globals.write_to_console) {
DWORD chars_written = 0;
WriteConsoleA(pap_globals.stdout_handle, string.data, (DWORD)string.size, &chars_written, NULL);
} else {
PrintHandle(pap_globals.stdout_handle, string);
}
}
void PrintFmt(char const *fmt, ...)
{
va_list args, args_copy;
va_start(args, fmt);
va_copy(args_copy, args);
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
char buffer[8192];
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
Str8 string = {.data = buffer, .size = string_size};
Print(string);
}
va_end(args);
}
void PrintLn(Str8 string)
{
Print(string);
Print(STR8("\n"));
}
void PrintLnFmt(char const *fmt, ...)
{
va_list args, args_copy;
va_start(args, fmt);
va_copy(args_copy, args);
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
char buffer[8192];
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
if (string_size) {
vsnprintf(buffer, sizeof(buffer), fmt, args);
Str8 string = {.data = buffer, .size = string_size};
PrintLn(string);
}
va_end(args);
}
+145
View File
@@ -0,0 +1,145 @@
#include <stdint.h>
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
#define STRINGIFY2(token) #token
#define STRINGIFY(token) STRINGIFY2(token)
#if defined(NDEBUG)
#define ASSERT(expr)
#else
#define ASSERT(expr) \
if (!(expr)) { \
PrintLnFmt("Assertion triggered [file=\"" __FILE__ ":" STRINGIFY(__LINE__) "\", expr=\"" #expr "\"]"); \
__debugbreak(); \
}
#endif
#define ARRAY_UCOUNT(array) sizeof((array)) / sizeof((array)[0])
#define CAST(Type) (Type)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
typedef float f32;
typedef double f64;
typedef bool b32;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
typedef struct Globals {
HANDLE stdout_handle;
bool write_to_console;
} Globals;
Globals pap_globals;
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
typedef struct Str8 {
char *data;
size_t size;
} Str8;
typedef struct Str8ToU64Result {
bool success;
uint64_t value;
} Str8ToU64Result;
typedef struct Str8BinarySplitResult {
Str8 lhs;
Str8 rhs;
} Str8BinarySplitResult;
#define STR8(string) (Str8){.data = (char *)(string), .size = ARRAY_UCOUNT(string) - 1 }
#define STR8_FMT(string) (int)((string).size), (string).data
bool Str8_Equals(Str8 lhs, Str8 rhs);
Str8ToU64Result Str8_ToU64(Str8 string);
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find);
bool CharIsWhiteSpace(char ch);
bool CharIsDigit(char ch);
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
typedef struct ProfilerAnchor {
Str8 label;
u64 elapsed_tsc_exclusive; // Does not include children
u64 elapsed_tsc_inclusive; // Includes children
u64 byte_count;
u64 hits;
} ProfilerAnchor;
typedef struct Profiler {
ProfilerAnchor anchors[4096];
u64 begin_tsc;
u64 end_tsc;
u64 parent_index;
} Profiler;
typedef struct ProfilerZone {
u64 parent_index;
uint32_t index;
Str8 label;
u64 elapsed_tsc_inclusive;
u64 tsc;
u64 byte_count;
} ProfilerZone;
static Profiler g_profiler;
#define Profiler_BeginZone(label) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, 0)
#define Profiler_BeginZoneBandwidth(label, byte_count) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, byte_count)
static void Profiler_Dump ();
static ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count);
static void Profiler_EndZone (ProfilerZone zone);
// NOTE: PCG32 /////////////////////////////////////////////////////////////////////////////////////
// NOTE: PCG RNG from Demetri Spanos: https://github.com/demetri/scribbles
// pcg32_pie, based on the minimal C version from O'Neill at pcg-random.org;
// I've made a few (subjective) UX improvements for beginner use
//
// I'm not allowing the user to pick the stream/increment constant at all,
// since there is almost never a reason for doing this in common applications.
// This means that the prng state is reduced to a single uint64_t which also
// means we can avoid having a state struct at all. The (fixed) stream constant
// uses the leading hex digits of pi and e multipled by 2^30 (c90fdaa2 and
// adf85459).
//
// I have also added an XOR with the same digits on the output path prior
// to xorshift mixing. This prevents the "surprising" result that the
// first "random 32-bit number" from a (very common) 0 seed is 0.
//
// use:
// uint64_t state = 12345; // whatever you like can go here
// uint32_t some_random_32_bits = pcg32_pie(&state);
// uint32_t more_random_32_bits = pcg32_pie(&state);
uint32_t PCG32_Pie (uint64_t *state);
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max);
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
typedef struct Buffer {
char *data;
size_t size;
} Buffer;
typedef struct BufferIterator {
Buffer buffer;
size_t index;
} BufferIterator;
bool BufferIsValid (Buffer buffer);
BufferIterator BufferIteratorInit (Buffer buffer);
bool BufferIteratorHasMoreBytes(BufferIterator it);
uint8_t BufferIteratorNextByte (BufferIterator *it);
// NOTE: File //////////////////////////////////////////////////////////////////////////////////////
Buffer FileRead (char const *file_path);
void FileFree (Buffer buffer);
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
// NOTE: Print /////////////////////////////////////////////////////////////////////////////////////
void PrintHandle(void *handle, Str8 string);
void PrintLn (Str8 string);
void PrintLnFmt (char const *fmt, ...);
+53
View File
@@ -0,0 +1,53 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 65
======================================================================== */
static f64 Square(f64 A)
{
f64 Result = (A*A);
return Result;
}
static f64 RadiansFromDegrees(f64 Degrees)
{
f64 Result = 0.01745329251994329577 * Degrees;
return Result;
}
// NOTE(casey): EarthRadius is generally expected to be 6372.8
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
{
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
question on which these homework exercises are loosely based.
*/
f64 lat1 = Y0;
f64 lat2 = Y1;
f64 lon1 = X0;
f64 lon2 = X1;
f64 dLat = RadiansFromDegrees(lat2 - lat1);
f64 dLon = RadiansFromDegrees(lon2 - lon1);
lat1 = RadiansFromDegrees(lat1);
lat2 = RadiansFromDegrees(lat2);
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
f64 c = 2.0*asin(sqrt(a));
f64 Result = EarthRadius * c;
return Result;
}
+72
View File
@@ -0,0 +1,72 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 68
======================================================================== */
struct buffer
{
size_t Count;
u8 *Data;
};
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
static b32 IsInBounds(buffer Source, u64 At)
{
b32 Result = (At < Source.Count);
return Result;
}
static b32 AreEqual(buffer A, buffer B)
{
if(A.Count != B.Count)
{
return false;
}
for(u64 Index = 0; Index < A.Count; ++Index)
{
if(A.Data[Index] != B.Data[Index])
{
return false;
}
}
return true;
}
static buffer AllocateBuffer(size_t Count)
{
buffer Result = {};
Result.Data = (u8 *)malloc(Count);
if(Result.Data)
{
Result.Count = Count;
}
else
{
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
}
return Result;
}
static void FreeBuffer(buffer *Buffer)
{
if(Buffer->Data)
{
free(Buffer->Data);
}
*Buffer = {};
}
+98
View File
@@ -0,0 +1,98 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 74
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
#endif
/* NOTE(casey): This does not need to be "inline", it could just be "static"
because compilers will inline it anyway. But compilers will warn about
static functions that aren't used. So "inline" is just the simplest way
to tell them to stop complaining about that. */
inline u64 ReadCPUTimer(void)
{
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
// with one of their performance counter read instructions, depending
// on which ones are available on your platform.
return __rdtsc();
}
static u64 EstimateCPUTimerFreq(void)
{
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
u64 CPUFreq = 0;
if(OSElapsed)
{
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
}
return CPUFreq;
}
@@ -0,0 +1,516 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 94
======================================================================== */
enum json_token_type
{
Token_end_of_stream,
Token_error,
Token_open_brace,
Token_open_bracket,
Token_close_brace,
Token_close_bracket,
Token_comma,
Token_colon,
Token_string_literal,
Token_number,
Token_true,
Token_false,
Token_null,
Token_count,
};
struct json_token
{
json_token_type Type;
buffer Value;
};
struct json_element
{
buffer Label;
buffer Value;
json_element *FirstSubElement;
json_element *NextSibling;
};
struct json_parser
{
buffer Source;
u64 At;
b32 HadError;
};
static b32 IsJSONDigit(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val >= '0') && (Val <= '9'));
}
return Result;
}
static b32 IsJSONWhitespace(buffer Source, u64 At)
{
b32 Result = false;
if(IsInBounds(Source, At))
{
u8 Val = Source.Data[At];
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
}
return Result;
}
static b32 IsParsing(json_parser *Parser)
{
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
return Result;
}
static void Error(json_parser *Parser, json_token Token, char const *Message)
{
Parser->HadError = true;
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
}
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
{
if((Source.Count - *At) >= KeywordRemaining.Count)
{
buffer Check = Source;
Check.Data += *At;
Check.Count = KeywordRemaining.Count;
if(AreEqual(Check, KeywordRemaining))
{
Result->Type = Type;
Result->Value.Count += KeywordRemaining.Count;
*At += KeywordRemaining.Count;
}
}
}
static json_token GetJSONToken(json_parser *Parser)
{
json_token Result = {};
buffer Source = Parser->Source;
u64 At = Parser->At;
while(IsJSONWhitespace(Source, At))
{
++At;
}
if(IsInBounds(Source, At))
{
Result.Type = Token_error;
Result.Value.Count = 1;
Result.Value.Data = Source.Data + At;
u8 Val = Source.Data[At++];
switch(Val)
{
case '{': {Result.Type = Token_open_brace;} break;
case '[': {Result.Type = Token_open_bracket;} break;
case '}': {Result.Type = Token_close_brace;} break;
case ']': {Result.Type = Token_close_bracket;} break;
case ',': {Result.Type = Token_comma;} break;
case ':': {Result.Type = Token_colon;} break;
case 'f':
{
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
} break;
case 'n':
{
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
} break;
case 't':
{
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
} break;
case '"':
{
Result.Type = Token_string_literal;
u64 StringStart = At;
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
{
if(IsInBounds(Source, (At + 1)) &&
(Source.Data[At] == '\\') &&
(Source.Data[At + 1] == '"'))
{
// NOTE(casey): Skip escaped quotation marks
++At;
}
++At;
}
Result.Value.Data = Source.Data + StringStart;
Result.Value.Count = At - StringStart;
if(IsInBounds(Source, At))
{
++At;
}
} break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
u64 Start = At - 1;
Result.Type = Token_number;
// NOTE(casey): Move past a leading negative sign if one exists
if((Val == '-') && IsInBounds(Source, At))
{
Val = Source.Data[At++];
}
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
if(Val != '0')
{
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
while(IsJSONDigit(Source, At))
{
++At;
}
}
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
{
++At;
}
while(IsJSONDigit(Source, At))
{
++At;
}
}
Result.Value.Count = At - Start;
} break;
default:
{
} break;
}
}
Parser->At = At;
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
{
b32 Valid = true;
json_element *SubElement = 0;
if(Value.Type == Token_open_bracket)
{
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
}
else if(Value.Type == Token_open_brace)
{
SubElement = ParseJSONList(Parser, Token_close_brace, true);
}
else if((Value.Type == Token_string_literal) ||
(Value.Type == Token_true) ||
(Value.Type == Token_false) ||
(Value.Type == Token_null) ||
(Value.Type == Token_number))
{
// NOTE(casey): Nothing to do here, since there is no additional data
}
else
{
Valid = false;
}
json_element *Result = 0;
if(Valid)
{
Result = (json_element *)malloc(sizeof(json_element));
Result->Label = Label;
Result->Value = Value.Value;
Result->FirstSubElement = SubElement;
Result->NextSibling = 0;
}
return Result;
}
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
{
json_element *FirstElement = {};
json_element *LastElement = {};
while(IsParsing(Parser))
{
buffer Label = {};
json_token Value = GetJSONToken(Parser);
if(HasLabels)
{
if(Value.Type == Token_string_literal)
{
Label = Value.Value;
json_token Colon = GetJSONToken(Parser);
if(Colon.Type == Token_colon)
{
Value = GetJSONToken(Parser);
}
else
{
Error(Parser, Colon, "Expected colon after field name");
}
}
else if(Value.Type != EndType)
{
Error(Parser, Value, "Unexpected token in JSON");
}
}
json_element *Element = ParseJSONElement(Parser, Label, Value);
if(Element)
{
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
}
else if(Value.Type == EndType)
{
break;
}
else
{
Error(Parser, Value, "Unexpected token in JSON");
}
json_token Comma = GetJSONToken(Parser);
if(Comma.Type == EndType)
{
break;
}
else if(Comma.Type != Token_comma)
{
Error(Parser, Comma, "Unexpected token in JSON");
}
}
return FirstElement;
}
static json_element *ParseJSON(buffer InputJSON)
{
TimeFunction;
json_parser Parser = {};
Parser.Source = InputJSON;
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
return Result;
}
static void FreeJSON(json_element *Element)
{
while(Element)
{
json_element *FreeElement = Element;
Element = Element->NextSibling;
FreeJSON(FreeElement->FirstSubElement);
free(FreeElement);
}
}
static json_element *LookupElement(json_element *Object, buffer ElementName)
{
json_element *Result = 0;
if(Object)
{
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
{
if(AreEqual(Search->Label, ElementName))
{
Result = Search;
break;
}
}
}
return Result;
}
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 1.0;
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
{
Result = -1.0;
++At;
}
*AtResult = At;
return Result;
}
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
{
u64 At = *AtResult;
f64 Result = 0.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Result = 10.0*Result + (f64)Char;
++At;
}
else
{
break;
}
}
*AtResult = At;
return Result;
}
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
{
f64 Result = 0.0;
json_element *Element = LookupElement(Object, ElementName);
if(Element)
{
buffer Source = Element->Value;
u64 At = 0;
f64 Sign = ConvertJSONSign(Source, &At);
f64 Number = ConvertJSONNumber(Source, &At);
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
{
++At;
f64 C = 1.0 / 10.0;
while(IsInBounds(Source, At))
{
u8 Char = Source.Data[At] - (u8)'0';
if(Char < 10)
{
Number = Number + C*(f64)Char;
C *= 1.0 / 10.0;
++At;
}
else
{
break;
}
}
}
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
{
++At;
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
{
++At;
}
f64 ExponentSign = ConvertJSONSign(Source, &At);
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
Number *= pow(10.0, Exponent);
}
Result = Sign*Number;
}
return Result;
}
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
{
TimeFunction;
u64 PairCount = 0;
json_element *JSON = ParseJSON(InputJSON);
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
if(PairsArray)
{
TimeBlock("Lookup and Convert");
for(json_element *Element = PairsArray->FirstSubElement;
Element && (PairCount < MaxPairCount);
Element = Element->NextSibling)
{
haversine_pair *Pair = Pairs + PairCount++;
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
}
}
{
TimeBlock("FreeJSON");
FreeJSON(JSON);
}
return PairCount;
}
+194
View File
@@ -0,0 +1,194 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 100
======================================================================== */
#include "listing_0074_platform_metrics.cpp"
#ifndef PROFILER
#define PROFILER 0
#endif
#ifndef READ_BLOCK_TIMER
#define READ_BLOCK_TIMER ReadCPUTimer
#endif
#if PROFILER
struct profile_anchor
{
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
u64 HitCount;
u64 ProcessedByteCount;
char const *Label;
};
static profile_anchor GlobalProfilerAnchors[4096];
static u32 GlobalProfilerParent;
struct profile_block
{
profile_block(char const *Label_, u32 AnchorIndex_, u64 ByteCount)
{
ParentIndex = GlobalProfilerParent;
AnchorIndex = AnchorIndex_;
Label = Label_;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
Anchor->ProcessedByteCount += ByteCount;
GlobalProfilerParent = AnchorIndex;
StartTSC = READ_BLOCK_TIMER();
}
~profile_block(void)
{
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
GlobalProfilerParent = ParentIndex;
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
Parent->TSCElapsedExclusive -= Elapsed;
Anchor->TSCElapsedExclusive += Elapsed;
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
++Anchor->HitCount;
/* NOTE(casey): This write happens every time solely because there is no
straightforward way in C++ to have the same ease-of-use. In a better programming
language, it would be simple to have the anchor points gathered and labeled at compile
time, and this repetative write would be eliminated. */
Anchor->Label = Label;
}
char const *Label;
u64 OldTSCElapsedInclusive;
u64 StartTSC;
u32 ParentIndex;
u32 AnchorIndex;
};
#define NameConcat2(A, B) A##B
#define NameConcat(A, B) NameConcat2(A, B)
#define TimeBandwidth(Name, ByteCount) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1, ByteCount)
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
static void PrintTimeElapsed(u64 TotalTSCElapsed, u64 TimerFreq, profile_anchor *Anchor)
{
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
{
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
printf(", %.2f%% w/children", PercentWithChildren);
}
printf(")");
if(Anchor->ProcessedByteCount)
{
f64 Megabyte = 1024.0f*1024.0f;
f64 Gigabyte = Megabyte*1024.0f;
f64 Seconds = (f64)Anchor->TSCElapsedInclusive / (f64)TimerFreq;
f64 BytesPerSecond = (f64)Anchor->ProcessedByteCount / Seconds;
f64 Megabytes = (f64)Anchor->ProcessedByteCount / (f64)Megabyte;
f64 GigabytesPerSecond = BytesPerSecond / Gigabyte;
printf(" %.3fmb at %.2fgb/s", Megabytes, GigabytesPerSecond);
}
printf("\n");
}
static void PrintAnchorData(u64 TotalCPUElapsed, u64 TimerFreq)
{
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
{
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
if(Anchor->TSCElapsedInclusive)
{
PrintTimeElapsed(TotalCPUElapsed, TimerFreq, Anchor);
}
}
}
#else
#define TimeBandwidth(...)
#define PrintAnchorData(...)
#define ProfilerEndOfCompilationUnit
#endif
struct profiler
{
u64 StartTSC;
u64 EndTSC;
};
static profiler GlobalProfiler;
#define TimeBlock(Name) TimeBandwidth(Name, 0)
#define TimeFunction TimeBlock(__func__)
static u64 EstimateBlockTimerFreq(void)
{
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 BlockStart = READ_BLOCK_TIMER();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 BlockEnd = READ_BLOCK_TIMER();
u64 BlockElapsed = BlockEnd - BlockStart;
u64 BlockFreq = 0;
if(OSElapsed)
{
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
}
return BlockFreq;
}
static void BeginProfile(void)
{
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
}
static void EndAndPrintProfile()
{
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
u64 TimerFreq = EstimateBlockTimerFreq();
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
if(TimerFreq)
{
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
}
PrintAnchorData(TotalTSCElapsed, TimerFreq);
}
+186
View File
@@ -0,0 +1,186 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 101
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
struct haversine_pair
{
f64 X0, Y0;
f64 X1, Y1;
};
#define PROFILER 1
#include "listing_0100_bandwidth_profiler.cpp"
#include "listing_0065_haversine_formula.cpp"
#include "listing_0068_buffer.cpp"
#include "listing_0094_profiled_lookup_json_parser.cpp"
static buffer ReadEntireFile(char *FileName)
{
TimeFunction;
buffer Result = {};
FILE *File = fopen(FileName, "rb");
if(File)
{
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
Result = AllocateBuffer(Stat.st_size);
if(Result.Data)
{
TimeBandwidth("fread", Result.Count);
if(fread(Result.Data, Result.Count, 1, File) != 1)
{
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
FreeBuffer(&Result);
}
}
fclose(File);
}
else
{
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
}
return Result;
}
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
{
TimeBandwidth(__func__, PairCount*sizeof(haversine_pair));
f64 Sum = 0;
f64 SumCoef = 1 / (f64)PairCount;
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
{
haversine_pair Pair = Pairs[PairIndex];
f64 EarthRadius = 6372.8;
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
Sum += SumCoef*Dist;
}
return Sum;
}
int main(int ArgCount, char **Args)
{
BeginProfile();
int Result = 1;
if((ArgCount == 2) || (ArgCount == 3))
{
buffer InputJSON = ReadEntireFile(Args[1]);
u32 MinimumJSONPairEncoding = 6*4;
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
if(MaxPairCount)
{
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
if(ParsedValues.Count)
{
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
f64 Sum = SumHaversineDistances(PairCount, Pairs);
Result = 0;
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
fprintf(stdout, "Pair count: %llu\n", PairCount);
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
if(ArgCount == 3)
{
buffer AnswersF64 = ReadEntireFile(Args[2]);
if(AnswersF64.Count >= sizeof(f64))
{
f64 *AnswerValues = (f64 *)AnswersF64.Data;
fprintf(stdout, "\nValidation:\n");
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
if(PairCount != RefAnswerCount)
{
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
}
f64 RefSum = AnswerValues[RefAnswerCount];
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
fprintf(stdout, "\n");
}
}
}
FreeBuffer(&ParsedValues);
}
else
{
fprintf(stderr, "ERROR: Malformed input JSON\n");
}
FreeBuffer(&InputJSON);
}
else
{
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
}
if(Result == 0)
{
EndAndPrintProfile();
}
return Result;
}
ProfilerEndOfCompilationUnit;
+151
View File
@@ -0,0 +1,151 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 102
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
struct read_parameters
{
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}
+211
View File
@@ -0,0 +1,211 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 103
======================================================================== */
enum test_mode : u32
{
TestMode_Uninitialized,
TestMode_Testing,
TestMode_Completed,
TestMode_Error,
};
struct repetition_test_results
{
u64 TestCount;
u64 TotalTime;
u64 MaxTime;
u64 MinTime;
};
struct repetition_tester
{
u64 TargetProcessedByteCount;
u64 CPUTimerFreq;
u64 TryForTime;
u64 TestsStartedAt;
test_mode Mode;
b32 PrintNewMinimums;
u32 OpenBlockCount;
u32 CloseBlockCount;
u64 TimeAccumulatedOnThisTest;
u64 BytesAccumulatedOnThisTest;
repetition_test_results Results;
};
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
{
f64 Result = 0.0;
if(CPUTimerFreq)
{
Result = (CPUTime / (f64)CPUTimerFreq);
}
return Result;
}
static void PrintTime(char const *Label, f64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
printf("%s: %.0f", Label, CPUTime);
if(CPUTimerFreq)
{
f64 Seconds = SecondsFromCPUTime(CPUTime, CPUTimerFreq);
printf(" (%fms)", 1000.0f*Seconds);
if(ByteCount)
{
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 BestBandwidth = ByteCount / (Gigabyte * Seconds);
printf(" %fgb/s", BestBandwidth);
}
}
}
static void PrintTime(char const *Label, u64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime(Label, (f64)CPUTime, CPUTimerFreq, ByteCount);
}
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq, u64 ByteCount)
{
PrintTime("Min", (f64)Results.MinTime, CPUTimerFreq, ByteCount);
printf("\n");
PrintTime("Max", (f64)Results.MaxTime, CPUTimerFreq, ByteCount);
printf("\n");
if(Results.TestCount)
{
PrintTime("Avg", (f64)Results.TotalTime / (f64)Results.TestCount, CPUTimerFreq, ByteCount);
printf("\n");
}
}
static void Error(repetition_tester *Tester, char const *Message)
{
Tester->Mode = TestMode_Error;
fprintf(stderr, "ERROR: %s\n", Message);
}
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
{
if(Tester->Mode == TestMode_Uninitialized)
{
Tester->Mode = TestMode_Testing;
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
Tester->CPUTimerFreq = CPUTimerFreq;
Tester->PrintNewMinimums = true;
Tester->Results.MinTime = (u64)-1;
}
else if(Tester->Mode == TestMode_Completed)
{
Tester->Mode = TestMode_Testing;
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
{
Error(Tester, "TargetProcessedByteCount changed");
}
if(Tester->CPUTimerFreq != CPUTimerFreq)
{
Error(Tester, "CPU frequency changed");
}
}
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
Tester->TestsStartedAt = ReadCPUTimer();
}
static void BeginTime(repetition_tester *Tester)
{
++Tester->OpenBlockCount;
Tester->TimeAccumulatedOnThisTest -= ReadCPUTimer();
}
static void EndTime(repetition_tester *Tester)
{
++Tester->CloseBlockCount;
Tester->TimeAccumulatedOnThisTest += ReadCPUTimer();
}
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
{
Tester->BytesAccumulatedOnThisTest += ByteCount;
}
static b32 IsTesting(repetition_tester *Tester)
{
if(Tester->Mode == TestMode_Testing)
{
u64 CurrentTime = ReadCPUTimer();
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
{
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
{
Error(Tester, "Unbalanced BeginTime/EndTime");
}
if(Tester->BytesAccumulatedOnThisTest != Tester->TargetProcessedByteCount)
{
Error(Tester, "Processed byte count mismatch");
}
if(Tester->Mode == TestMode_Testing)
{
repetition_test_results *Results = &Tester->Results;
u64 ElapsedTime = Tester->TimeAccumulatedOnThisTest;
Results->TestCount += 1;
Results->TotalTime += ElapsedTime;
if(Results->MaxTime < ElapsedTime)
{
Results->MaxTime = ElapsedTime;
}
if(Results->MinTime > ElapsedTime)
{
Results->MinTime = ElapsedTime;
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
Tester->TestsStartedAt = CurrentTime;
if(Tester->PrintNewMinimums)
{
PrintTime("Min", Results->MinTime, Tester->CPUTimerFreq, Tester->BytesAccumulatedOnThisTest);
printf(" \r");
}
}
Tester->OpenBlockCount = 0;
Tester->CloseBlockCount = 0;
Tester->TimeAccumulatedOnThisTest = 0;
Tester->BytesAccumulatedOnThisTest = 0;
}
}
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
{
Tester->Mode = TestMode_Completed;
printf(" \r");
PrintResults(Tester->Results, Tester->CPUTimerFreq, Tester->TargetProcessedByteCount);
}
}
b32 Result = (Tester->Mode == TestMode_Testing);
return Result;
}
+116
View File
@@ -0,0 +1,116 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 104
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,116 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 105
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0102_read_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"ReadFile", ReadViaReadFile},
{"_read", ReadViaRead},
{"fread", ReadViaFRead},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
repetition_tester *Tester = Testers + FuncIndex;
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s ---\n", TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,219 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 106
======================================================================== */
#include <windows.h>
#include <fcntl.h>
#include <io.h>
enum allocation_type
{
AllocType_none,
AllocType_malloc,
AllocType_Count,
};
struct read_parameters
{
allocation_type AllocType;
buffer Dest;
char const *FileName;
};
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
static char const *DescribeAllocationType(allocation_type AllocType)
{
char const *Result;
switch(AllocType)
{
case AllocType_none: {Result = "";} break;
case AllocType_malloc: {Result = "malloc";} break;
default : {Result = "UNKNOWN";} break;
}
return Result;
}
static void HandleAllocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
*Buffer = AllocateBuffer(Params->Dest.Count);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void HandleDeallocation(read_parameters *Params, buffer *Buffer)
{
switch(Params->AllocType)
{
case AllocType_none:
{
} break;
case AllocType_malloc:
{
FreeBuffer(Buffer);
} break;
default:
{
fprintf(stderr, "ERROR: Unrecognized allocation type");
} break;
}
}
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
FILE *File = fopen(Params->FileName, "rb");
if(File)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
BeginTime(Tester);
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
EndTime(Tester);
if(Result == 1)
{
CountBytes(Tester, DestBuffer.Count);
}
else
{
Error(Tester, "fread failed");
}
HandleDeallocation(Params, &DestBuffer);
fclose(File);
}
else
{
Error(Tester, "fopen failed");
}
}
}
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
if(File != -1)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u8 *Dest = DestBuffer.Data;
u64 SizeRemaining = DestBuffer.Count;
while(SizeRemaining)
{
u32 ReadSize = INT_MAX;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
BeginTime(Tester);
int Result = _read(File, Dest, ReadSize);
EndTime(Tester);
if(Result == (int)ReadSize)
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "_read failed");
break;
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
_close(File);
}
else
{
Error(Tester, "_open failed");
}
}
}
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(File != INVALID_HANDLE_VALUE)
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
u64 SizeRemaining = Params->Dest.Count;
u8 *Dest = (u8 *)DestBuffer.Data;
while(SizeRemaining)
{
u32 ReadSize = (u32)-1;
if((u64)ReadSize > SizeRemaining)
{
ReadSize = (u32)SizeRemaining;
}
DWORD BytesRead = 0;
BeginTime(Tester);
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
EndTime(Tester);
if(Result && (BytesRead == ReadSize))
{
CountBytes(Tester, ReadSize);
}
else
{
Error(Tester, "ReadFile failed");
}
SizeRemaining -= ReadSize;
Dest += ReadSize;
}
HandleDeallocation(Params, &DestBuffer);
CloseHandle(File);
}
else
{
Error(Tester, "CreateFileA failed");
}
}
}
@@ -0,0 +1,124 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 107
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0074_platform_metrics.cpp"
#include "listing_0103_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
+145
View File
@@ -0,0 +1,145 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 108
======================================================================== */
#if _WIN32
#include <intrin.h>
#include <windows.h>
#include <psapi.h>
typedef struct os_metrics
{
b32 Initialized;
HANDLE ProcessHandle;
} os_metrics;
static os_metrics GlobalMetrics;
static u64 GetOSTimerFreq(void)
{
LARGE_INTEGER Freq;
QueryPerformanceFrequency(&Freq);
return Freq.QuadPart;
}
static u64 ReadOSTimer(void)
{
LARGE_INTEGER Value;
QueryPerformanceCounter(&Value);
return Value.QuadPart;
}
static u64 ReadOSPageFaultCount(void)
{
PROCESS_MEMORY_COUNTERS_EX MemoryCounters = {};
MemoryCounters.cb = sizeof(MemoryCounters);
GetProcessMemoryInfo(GlobalMetrics.ProcessHandle, (PROCESS_MEMORY_COUNTERS *)&MemoryCounters, sizeof(MemoryCounters));
u64 Result = MemoryCounters.PageFaultCount;
return Result;
}
static void InitializeOSMetrics(void)
{
if(!GlobalMetrics.Initialized)
{
GlobalMetrics.Initialized = true;
GlobalMetrics.ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
}
}
#else
#include <x86intrin.h>
#include <sys/time.h>
static u64 GetOSTimerFreq(void)
{
return 1000000;
}
static u64 ReadOSTimer(void)
{
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
// but just in case anyone is using this file from C, I include it.
struct timeval Value;
gettimeofday(&Value, 0);
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
return Result;
}
static u64 ReadOSPageFaultCount(void)
{
// NOTE(casey): The course materials are not tested on MacOS/Linux.
// This code was contributed to the public github. It may or may not work
// for your system.
struct rusage Usage = {};
getrusage(RUSAGE_SELF, &Usage);
// ru_minflt the number of page faults serviced without any I/O activity.
// ru_majflt the number of page faults serviced that required I/O activity.
u64 Result = Usage.ru_minflt + Usage.ru_majflt;
return Result;
}
static void InitializeOSMetrics(void)
{
}
#endif
/* NOTE(casey): This does not need to be "inline", it could just be "static"
because compilers will inline it anyway. But compilers will warn about
static functions that aren't used. So "inline" is just the simplest way
to tell them to stop complaining about that. */
inline u64 ReadCPUTimer(void)
{
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
// with one of their performance counter read instructions, depending
// on which ones are available on your platform.
return __rdtsc();
}
static u64 EstimateCPUTimerFreq(void)
{
u64 MillisecondsToWait = 100;
u64 OSFreq = GetOSTimerFreq();
u64 CPUStart = ReadCPUTimer();
u64 OSStart = ReadOSTimer();
u64 OSEnd = 0;
u64 OSElapsed = 0;
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
while(OSElapsed < OSWaitTime)
{
OSEnd = ReadOSTimer();
OSElapsed = OSEnd - OSStart;
}
u64 CPUEnd = ReadCPUTimer();
u64 CPUElapsed = CPUEnd - CPUStart;
u64 CPUFreq = 0;
if(OSElapsed)
{
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
}
return CPUFreq;
}
@@ -0,0 +1,240 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 109
======================================================================== */
enum test_mode : u32
{
TestMode_Uninitialized,
TestMode_Testing,
TestMode_Completed,
TestMode_Error,
};
enum repetition_value_type
{
RepValue_TestCount,
RepValue_CPUTimer,
RepValue_MemPageFaults,
RepValue_ByteCount,
RepValue_Count,
};
struct repetition_value
{
u64 E[RepValue_Count];
};
struct repetition_test_results
{
repetition_value Total;
repetition_value Min;
repetition_value Max;
};
struct repetition_tester
{
u64 TargetProcessedByteCount;
u64 CPUTimerFreq;
u64 TryForTime;
u64 TestsStartedAt;
test_mode Mode;
b32 PrintNewMinimums;
u32 OpenBlockCount;
u32 CloseBlockCount;
repetition_value AccumulatedOnThisTest;
repetition_test_results Results;
};
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
{
f64 Result = 0.0;
if(CPUTimerFreq)
{
Result = (CPUTime / (f64)CPUTimerFreq);
}
return Result;
}
static void PrintValue(char const *Label, repetition_value Value, u64 CPUTimerFreq)
{
u64 TestCount = Value.E[RepValue_TestCount];
f64 Divisor = TestCount ? (f64)TestCount : 1;
f64 E[RepValue_Count];
for(u32 EIndex = 0; EIndex < ArrayCount(E); ++EIndex)
{
E[EIndex] = (f64)Value.E[EIndex] / Divisor;
}
printf("%s: %.0f", Label, E[RepValue_CPUTimer]);
if(CPUTimerFreq)
{
f64 Seconds = SecondsFromCPUTime(E[RepValue_CPUTimer], CPUTimerFreq);
printf(" (%fms)", 1000.0f*Seconds);
if(E[RepValue_ByteCount] > 0)
{
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 Bandwidth = E[RepValue_ByteCount] / (Gigabyte * Seconds);
printf(" %fgb/s", Bandwidth);
}
}
if(E[RepValue_MemPageFaults] > 0)
{
printf(" PF: %0.4f (%0.4fk/fault)", E[RepValue_MemPageFaults], E[RepValue_ByteCount] / (E[RepValue_MemPageFaults] * 1024.0));
}
}
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq)
{
PrintValue("Min", Results.Min, CPUTimerFreq);
printf("\n");
PrintValue("Max", Results.Max, CPUTimerFreq);
printf("\n");
PrintValue("Avg", Results.Total, CPUTimerFreq);
printf("\n");
}
static void Error(repetition_tester *Tester, char const *Message)
{
Tester->Mode = TestMode_Error;
fprintf(stderr, "ERROR: %s\n", Message);
}
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
{
if(Tester->Mode == TestMode_Uninitialized)
{
Tester->Mode = TestMode_Testing;
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
Tester->CPUTimerFreq = CPUTimerFreq;
Tester->PrintNewMinimums = true;
Tester->Results.Min.E[RepValue_CPUTimer] = (u64)-1;
}
else if(Tester->Mode == TestMode_Completed)
{
Tester->Mode = TestMode_Testing;
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
{
Error(Tester, "TargetProcessedByteCount changed");
}
if(Tester->CPUTimerFreq != CPUTimerFreq)
{
Error(Tester, "CPU frequency changed");
}
}
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
Tester->TestsStartedAt = ReadCPUTimer();
}
static void BeginTime(repetition_tester *Tester)
{
++Tester->OpenBlockCount;
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_MemPageFaults] -= ReadOSPageFaultCount();
Accum->E[RepValue_CPUTimer] -= ReadCPUTimer();
}
static void EndTime(repetition_tester *Tester)
{
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_CPUTimer] += ReadCPUTimer();
Accum->E[RepValue_MemPageFaults] += ReadOSPageFaultCount();
++Tester->CloseBlockCount;
}
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
{
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
Accum->E[RepValue_ByteCount] += ByteCount;
}
static b32 IsTesting(repetition_tester *Tester)
{
if(Tester->Mode == TestMode_Testing)
{
repetition_value Accum = Tester->AccumulatedOnThisTest;
u64 CurrentTime = ReadCPUTimer();
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
{
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
{
Error(Tester, "Unbalanced BeginTime/EndTime");
}
if(Accum.E[RepValue_ByteCount] != Tester->TargetProcessedByteCount)
{
Error(Tester, "Processed byte count mismatch");
}
if(Tester->Mode == TestMode_Testing)
{
repetition_test_results *Results = &Tester->Results;
Accum.E[RepValue_TestCount] = 1;
for(u32 EIndex = 0; EIndex < ArrayCount(Accum.E); ++EIndex)
{
Results->Total.E[EIndex] += Accum.E[EIndex];
}
if(Results->Max.E[RepValue_CPUTimer] < Accum.E[RepValue_CPUTimer])
{
Results->Max = Accum;
}
if(Results->Min.E[RepValue_CPUTimer] > Accum.E[RepValue_CPUTimer])
{
Results->Min = Accum;
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
Tester->TestsStartedAt = CurrentTime;
if(Tester->PrintNewMinimums)
{
PrintValue("Min", Results->Min, Tester->CPUTimerFreq);
printf(" \r");
}
}
Tester->OpenBlockCount = 0;
Tester->CloseBlockCount = 0;
Tester->AccumulatedOnThisTest = {};
}
}
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
{
Tester->Mode = TestMode_Completed;
printf(" \r");
PrintResults(Tester->Results, Tester->CPUTimerFreq);
}
}
b32 Result = (Tester->Mode == TestMode_Testing);
return Result;
}
@@ -0,0 +1,35 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 110
======================================================================== */
static void WriteToAllBytes(repetition_tester *Tester, read_parameters *Params)
{
while(IsTesting(Tester))
{
buffer DestBuffer = Params->Dest;
HandleAllocation(Params, &DestBuffer);
BeginTime(Tester);
for(u64 Index = 0; Index < DestBuffer.Count; ++Index)
{
DestBuffer.Data[Index] = (u8)Index;
}
EndTime(Tester);
CountBytes(Tester, DestBuffer.Count);
HandleDeallocation(Params, &DestBuffer);
}
}
@@ -0,0 +1,127 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 111
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <sys/stat.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0068_buffer.cpp"
#include "listing_0108_platform_metrics.cpp"
#include "listing_0109_pagefault_repetition_tester.cpp"
#include "listing_0106_mallocread_overhead_test.cpp"
#include "listing_0110_pagefault_overhead_test.cpp"
struct test_function
{
char const *Name;
read_overhead_test_func *Func;
};
test_function TestFunctions[] =
{
{"WriteToAllBytes", WriteToAllBytes},
{"fread", ReadViaFRead},
{"_read", ReadViaRead},
{"ReadFile", ReadViaReadFile},
};
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&IsInBounds;
(void)&AreEqual;
InitializeOSMetrics();
u64 CPUTimerFreq = EstimateCPUTimerFreq();
if(ArgCount == 2)
{
char *FileName = Args[1];
#if _WIN32
struct __stat64 Stat;
_stat64(FileName, &Stat);
#else
struct stat Stat;
stat(FileName, &Stat);
#endif
read_parameters Params = {};
Params.Dest = AllocateBuffer(Stat.st_size);
Params.FileName = FileName;
if(Params.Dest.Count > 0)
{
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
for(;;)
{
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
{
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
{
Params.AllocType = (allocation_type)AllocType;
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
test_function TestFunc = TestFunctions[FuncIndex];
printf("\n--- %s%s%s ---\n",
DescribeAllocationType(Params.AllocType),
Params.AllocType ? " + " : "",
TestFunc.Name);
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
TestFunc.Func(Tester, &Params);
}
}
}
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
(void)&FreeBuffer;
}
else
{
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
}
}
else
{
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,89 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 112
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}
@@ -0,0 +1,89 @@
/* ========================================================================
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Please see https://computerenhance.com for more information
======================================================================== */
/* ========================================================================
LISTING 113
======================================================================== */
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
then the code doesn't compile on Linux anymore, since fopen_s() does not
exist there.
What exactly the CRT maintainers were thinking when they made this choice,
I have no idea. */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int32_t b32;
typedef float f32;
typedef double f64;
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
#include "listing_0108_platform_metrics.cpp"
int main(int ArgCount, char **Args)
{
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
// here to prevent the compiler from complaining about "unused functions".
(void)&EstimateCPUTimerFreq;
InitializeOSMetrics();
if(ArgCount == 2)
{
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
u64 PageCount = atol(Args[1]);
u64 TotalSize = PageSize*PageCount;
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
{
u64 TouchSize = PageSize*TouchCount;
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Data)
{
u64 StartFaultCount = ReadOSPageFaultCount();
for(u64 Index = 0; Index < TouchSize; ++Index)
{
Data[TotalSize - 1 - Index] = (u8)Index;
}
u64 EndFaultCount = ReadOSPageFaultCount();
u64 FaultCount = EndFaultCount - StartFaultCount;
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
VirtualFree(Data, 0, MEM_RELEASE);
}
else
{
fprintf(stderr, "ERROR: Unable to allocate memory\n");
}
}
}
else
{
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
}
return 0;
}
+385
View File
@@ -0,0 +1,385 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdbool.h>
#include <stdio.h>
#include <Windows.h>
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#include "base.h"
#include "listing_0108_platform_metrics.cpp"
#include "base.c"
#include "repetition_tester.h"
// NOTE: Allocate //////////////////////////////////////////////////////////////////////////////////
Str8 AllocTypeStr8(AllocType type)
{
Str8 result = {};
switch (type) {
case AllocType_None: result = STR8("Pre-Allocated"); break;
case AllocType_VirtualAlloc: result = STR8("VirtualAlloc"); break;
case AllocType_Malloc: result = STR8("Malloc"); break;
case AllocType_Count: break;
}
return result;
}
void TestAlloc(ReadArgs *args, Buffer *buffer)
{
switch (args->alloc_type) {
case AllocType_None: break;
case AllocType_VirtualAlloc: {
buffer->data = VirtualAlloc(/*LPVOID lpAddress*/ NULL,
/*SIZE_T dwSize*/ buffer->size,
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
/*DWORD flProtect*/ PAGE_READWRITE);
} break;
case AllocType_Malloc: {
buffer->data = malloc(buffer->size);
} break;
case AllocType_Count: break;
}
}
void TestDealloc(ReadArgs *args, Buffer *buffer)
{
switch (args->alloc_type) {
case AllocType_None: break;
case AllocType_Malloc: free(buffer->data); break;
case AllocType_VirtualAlloc: VirtualFree(buffer->data, 0, MEM_RELEASE); break;
case AllocType_Count: break;
}
}
// NOTE: RepTester /////////////////////////////////////////////////////////////////////////////////
void RepTester_Error(RepTester *tester, Str8 msg)
{
tester->mode = RepTesterMode_Error;
fprintf(stderr, "ERROR: %.*s\n", STR8_FMT(msg));
}
void RepTester_BeginTime(RepTester *tester)
{
tester->open_block_count++;
tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] -= ReadCPUTimer();
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] -= ReadOSPageFaultCount();
}
void RepTester_EndTime(RepTester *tester)
{
tester->close_block_count++;
tester->accumulated_on_this_test.e[RepTesterValueType_CPUTimer] += ReadCPUTimer();
tester->accumulated_on_this_test.e[RepTesterValueType_MemPageFaults] += ReadOSPageFaultCount();
}
void RepTester_CountBytes(RepTester *tester, size_t bytes_read)
{
tester->accumulated_on_this_test.e[RepTesterValueType_ByteCount] += bytes_read;
}
static void PrintTime(Str8 label, f64 cpu_time, u64 cpu_timer_freq, u64 byte_count)
{
printf("%.*s: %.0f", STR8_FMT(label), cpu_time);
if (cpu_timer_freq) {
f64 seconds = cpu_time / CAST(f64)cpu_timer_freq;
printf(" (%fms)", 1000.0f * seconds);
if (byte_count) {
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 best_bandwidth = byte_count / (gigabyte * seconds);
printf(" %fgb/s", best_bandwidth);
}
}
}
static void RepTester_PrintValue(Str8 label, RepTesterValue value, u64 cpu_timer_freq)
{
u64 test_count = value.e[RepTesterValueType_TestCount];
f64 divisor = test_count ? (f64)test_count : 1;
f64 avg_values[ARRAY_UCOUNT(value.e)];
for (u64 index = 0; index < ARRAY_UCOUNT(value.e); index++)
avg_values[index] = value.e[index] / divisor;
printf("%.*s: %.0f", STR8_FMT(label), avg_values[RepTesterValueType_CPUTimer]);
if (cpu_timer_freq) {
f64 seconds = avg_values[RepTesterValueType_CPUTimer] / cpu_timer_freq;
printf(" (%fms)", 1000.0f*seconds);
if (avg_values[RepTesterValueType_ByteCount] > 0) {
f64 gigabyte = (1024.0f * 1024.0f * 1024.0f);
f64 bandwidth = avg_values[RepTesterValueType_ByteCount] / (gigabyte * seconds);
printf(" %fgb/s", bandwidth);
}
}
if(avg_values[RepTesterValueType_MemPageFaults] > 0) {
printf(" PF: %0.4f (%0.4fk/fault)",
avg_values[RepTesterValueType_MemPageFaults],
avg_values[RepTesterValueType_ByteCount] / (avg_values[RepTesterValueType_MemPageFaults] * 1024.0));
}
}
static void RepTester_PrintResult(RepTesterResults results, u64 cpu_timer_freq)
{
RepTester_PrintValue(STR8("Min"), results.min, cpu_timer_freq);
printf("\n");
RepTester_PrintValue(STR8("Max"), results.max, cpu_timer_freq);
printf("\n");
RepTester_PrintValue(STR8("Avg"), results.total, cpu_timer_freq);
printf("\n");
}
bool RepTester_IsTesting(RepTester *tester)
{
if (tester->mode == RepTesterMode_Testing) {
u64 current_time = ReadCPUTimer();
if (tester->open_block_count) {
if (tester->open_block_count != tester->close_block_count)
RepTester_Error(tester, STR8("Unbalanced begin/end time"));
RepTesterValue accum = tester->accumulated_on_this_test;
if (accum.e[RepTesterValueType_ByteCount] != tester->desired_bytes_read)
RepTester_Error(tester, STR8("Processed byte count mismatch"));
if (tester->mode == RepTesterMode_Testing) {
RepTesterResults *results = &tester->results;
accum.e[RepTesterValueType_TestCount] = 1;
for (u64 index = 0; index < ARRAY_UCOUNT(accum.e); index++) {
results->total.e[index] += accum.e[index];
}
if (results->max.e[RepTesterValueType_CPUTimer] < accum.e[RepTesterValueType_CPUTimer]) {
results->max = accum;
}
if (results->min.e[RepTesterValueType_CPUTimer] > accum.e[RepTesterValueType_CPUTimer]) {
results->min = accum;
// NOTE: Reset the trial time when new min time is achieved
tester->start_time = current_time;
RepTester_PrintValue(STR8("Min"), results->min, tester->cpu_timer_freq);
printf(" \r");
}
tester->open_block_count = 0;
tester->close_block_count = 0;
tester->accumulated_on_this_test = (RepTesterValue){};
}
}
if ((current_time - tester->start_time) > tester->run_duration) {
tester->mode = RepTesterMode_Complete;
printf(" \r");
RepTester_PrintResult(tester->results, tester->cpu_timer_freq);
}
}
bool result = tester->mode == RepTesterMode_Testing;
return result;
}
// NOTE: Read testing functions ////////////////////////////////////////////////////////////////////
static void ReadWithFRead(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
FILE *file = fopen(args->file_name.data, "rb");
if (file) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
RepTester_BeginTime(tester);
size_t result = fread(buffer.data, buffer.size, 1, file);
RepTester_EndTime(tester);
if (result == 1) {
RepTester_CountBytes(tester, buffer.size);
} else {
RepTester_Error(tester, STR8("fopen failed"));
}
TestDealloc(args, &buffer);
fclose(file);
} else {
RepTester_Error(tester, STR8("fopen failed"));
}
}
}
static void ReadWithRead(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
int file = _open(args->file_name.data, _O_BINARY | _O_RDONLY);
if (file != -1) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
char *dest = buffer.data;
u64 space_remaining = buffer.size;
while (space_remaining) {
u32 read_size = INT_MAX;
if ((u64)read_size > space_remaining)
read_size = (u32)space_remaining;
RepTester_BeginTime(tester);
int result = _read(file, dest, read_size);
RepTester_EndTime(tester);
if (result == (int)read_size) {
RepTester_CountBytes(tester, read_size);
} else {
RepTester_Error(tester, STR8("_read failed"));
break;
}
space_remaining -= read_size;
dest += read_size;
}
TestDealloc(args, &buffer);
_close(file);
} else {
RepTester_Error(tester, STR8("_open failed"));
}
}
}
static void ReadWithReadFile(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
HANDLE file = CreateFileA(args->file_name.data,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (file != INVALID_HANDLE_VALUE) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
char *dest = buffer.data;
u64 space_remaining = buffer.size;
while (space_remaining) {
u32 read_size = UINT32_MAX;
if ((u64)read_size > space_remaining)
read_size = (u32)space_remaining;
DWORD bytes_read = 0;
RepTester_BeginTime(tester);
BOOL result = ReadFile(file, dest, read_size, &bytes_read, 0);
RepTester_EndTime(tester);
if (result && (bytes_read == read_size))
RepTester_CountBytes(tester, read_size);
else
RepTester_Error(tester, STR8("ReadFile failed"));
space_remaining -= read_size;
dest += read_size;
}
TestDealloc(args, &buffer);
CloseHandle(file);
} else {
RepTester_Error(tester, STR8("CreateFileA failed"));
}
}
}
static void WriteToAllBytes(RepTester *tester, ReadArgs *args)
{
while (RepTester_IsTesting(tester)) {
Buffer buffer = args->dest;
TestAlloc(args, &buffer);
RepTester_BeginTime(tester);
for (u64 index = 0; index < buffer.size; index++)
buffer.data[index] = (u8)index;
RepTester_EndTime(tester);
RepTester_CountBytes(tester, buffer.size);
TestDealloc(args, &buffer);
}
}
typedef void TestFuncPtr(RepTester *tester, ReadArgs *args);
typedef struct TestFunction {
Str8 name;
TestFuncPtr *func;
} TestFunction;
int main(int argc, char const **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s [existing filename]\n", argv[0]);
return -1;
}
Str8 file_name = {.data = CAST(char *) argv[1], .size = strlen(argv[1])};
struct __stat64 stat;
_stat64(file_name.data, &stat);
ReadArgs args = {};
args.dest.data = VirtualAlloc(NULL, stat.st_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
args.dest.size = stat.st_size;
args.file_name = file_name;
if (stat.st_size <= 0) {
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
return -1;
}
if (!args.dest.data) {
fprintf(stderr, "ERROR: Failed to allocate %zu bytes\n", stat.st_size);
return -1;
}
InitializeOSMetrics();
TestFunction test_functions[] = {
{STR8("WriteToAllBytes"), WriteToAllBytes},
{STR8("fread"), ReadWithFRead},
{STR8("_read"), ReadWithRead},
{STR8("ReadFile"), ReadWithReadFile},
};
RepTester testers[ARRAY_UCOUNT(test_functions)][AllocType_Count] = {};
u64 cpu_timer_freq = EstimateCPUTimerFreq();
for (u64 index = 0; index != UINT64_MAX; index++) {
for (u64 func_index = 0; func_index < ARRAY_UCOUNT(testers); func_index++) {
for (u64 alloc_index = 0; alloc_index < AllocType_Count; alloc_index++) {
RepTester *tester = &testers[func_index][alloc_index];
TestFunction test_func = test_functions[func_index];
printf("\n--- %.*s + %.*s ---\n",
STR8_FMT(AllocTypeStr8((AllocType)alloc_index)),
STR8_FMT(test_func.name));
if (tester->mode == RepTesterMode_Nil) {
tester->mode = RepTesterMode_Testing;
tester->desired_bytes_read = args.dest.size;
tester->cpu_timer_freq = cpu_timer_freq;
tester->results.min.e[RepTesterValueType_CPUTimer] = (u64)-1;
} else if (tester->mode == RepTesterMode_Complete) {
tester->mode = RepTesterMode_Testing;
if (tester->desired_bytes_read != args.dest.size)
RepTester_Error(tester, STR8("desired_bytes_read changed"));
if (tester->cpu_timer_freq != cpu_timer_freq)
RepTester_Error(tester, STR8("cpu frequency changed"));
}
tester->run_duration = /*seconds_to_try*/ 10 * cpu_timer_freq;
tester->start_time = ReadCPUTimer();
args.alloc_type = alloc_index;
test_func.func(tester, &args);
}
}
}
return 0;
}
+52
View File
@@ -0,0 +1,52 @@
typedef enum RepTesterMode {
RepTesterMode_Nil,
RepTesterMode_Testing,
RepTesterMode_Error,
RepTesterMode_Complete,
} RepTesterMode;
typedef enum RepTesterValueType {
RepTesterValueType_TestCount,
RepTesterValueType_CPUTimer,
RepTesterValueType_MemPageFaults,
RepTesterValueType_ByteCount,
RepTesterValueType_Count,
} RepTesterValueType;
typedef struct RepTesterValue {
u64 e[RepTesterValueType_Count];
} RepTesterValue;
typedef struct RepTesterResults {
RepTesterValue total;
RepTesterValue max;
RepTesterValue min;
} RepTesterResults;
typedef struct RepTester {
RepTesterMode mode;
u32 open_block_count;
u32 close_block_count;
u64 cpu_timer_freq;
RepTesterResults results;
RepTesterValue accumulated_on_this_test;
size_t desired_bytes_read;
u64 start_time;
u64 run_duration;
} RepTester;
typedef enum AllocType {
AllocType_None,
AllocType_VirtualAlloc,
AllocType_Malloc,
AllocType_Count,
} AllocType;
typedef struct ReadArgs {
Buffer dest;
Str8 file_name;
AllocType alloc_type;
} ReadArgs;
BIN
View File
Binary file not shown.