From 32727f986c416f1c5beb32dd1014286dce9336e0 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sat, 2 Nov 2024 21:22:53 +1100 Subject: [PATCH] Benchmarked and tweaked Toy_Table, read more I read the DOOM 1 source code and found a neat trick to replace modulo. YOINK!!! --- .github/workflows/benchmarks.yml | 24 ---- .../workflows/continuous-integration-v2.yml | 26 +--- makefile | 13 +- source/toy_table.c | 14 ++- tests/benchmarks/array.c | 26 ---- tests/benchmarks/gdb_init | 1 - tests/benchmarks/modulo_hack/bench_main.c | 60 +++++++++ tests/benchmarks/modulo_hack/makefile | 115 ++++++++++++++++++ 8 files changed, 188 insertions(+), 91 deletions(-) delete mode 100644 .github/workflows/benchmarks.yml delete mode 100644 tests/benchmarks/array.c create mode 100644 tests/benchmarks/modulo_hack/bench_main.c create mode 100644 tests/benchmarks/modulo_hack/makefile diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml deleted file mode 100644 index 30979d2..0000000 --- a/.github/workflows/benchmarks.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Benchmarks - -#trigger when these occur -on: - workflow_dispatch: - -#Benchmarks are currently only supported on one platform -jobs: - run-test-cases: - continue-on-error: true - strategy: - matrix: - platforms: - - { os: ubuntu-latest, preinstall: sudo apt-get install time } - commands: - - { exec: make -C tests/benchmarks -k } - - runs-on: ${{ matrix.platforms.os }} - steps: - - uses: actions/checkout@v4 - - name: Preinstall dependencies - run: ${{ matrix.platforms.preinstall }} - - name: run the tests - run: ${{ matrix.commands.exec }} diff --git a/.github/workflows/continuous-integration-v2.yml b/.github/workflows/continuous-integration-v2.yml index 39fc9d8..9c9e7c2 100644 --- a/.github/workflows/continuous-integration-v2.yml +++ b/.github/workflows/continuous-integration-v2.yml @@ -60,27 +60,5 @@ jobs: - name: run the tests if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true run: ${{ matrix.commands.exec }} - - run-test-benchmarks: - if: false #Not ready yet - needs: run-test-integrations - continue-on-error: true - strategy: - matrix: - platforms: - - { os: ubuntu-latest, preinstall: sudo apt-get install gdb, gdb_enabled: true } - - { os: windows-latest, preinstall: , gdb_enabled: true } - - { os: macos-latest, preinstall: , gdb_enabled: false } - commands: - - { exec: make test-benchmarks, gdb: false } - - { exec: make test-benchmarks-gdb, gdb: true } - - runs-on: ${{ matrix.platforms.os }} - steps: - - uses: actions/checkout@v4 - - name: Preinstall dependencies - if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true - run: ${{ matrix.platforms.preinstall }} - - name: run the tests - if: (matrix.commands.gdb == true && matrix.platforms.gdb_enabled == false) != true - run: ${{ matrix.commands.exec }} + + diff --git a/makefile b/makefile index bbab68e..04e27ff 100644 --- a/makefile +++ b/makefile @@ -9,7 +9,6 @@ export TOY_SOURCEDIR=source export TOY_REPLDIR=repl export TOY_CASESDIR=tests/cases export TOY_INTEGRATIONSDIR=tests/integrations -export TOY_BENCHMARKSDIR=tests/benchmarks export TOY_OUTDIR=out export TOY_OBJDIR=obj @@ -29,7 +28,7 @@ repl: source tests: clean test-cases test-integrations .PHONY: test-all -test-all: clean test-cases test-integrations test-benchmarks +test-all: clean test-cases test-integrations .PHONY: test-cases test-cases: @@ -39,13 +38,9 @@ test-cases: test-integrations: $(MAKE) -C $(TOY_INTEGRATIONSDIR) -k -.PHONY: test-benchmarks -test-benchmarks: - $(MAKE) -C $(TOY_BENCHMARKSDIR) -k - #same as above, but with GDB .PHONY: test-gdb -test-gdb: clean test-cases-gdb test-integrations-gdb test-benchmarks-gdb +test-gdb: clean test-cases-gdb test-integrations-gdb .PHONY: test-cases-gdb test-cases-gdb: @@ -55,10 +50,6 @@ test-cases-gdb: test-integrations-gdb: $(MAKE) -C $(TOY_INTEGRATIONSDIR) gdb -k -.PHONY: test-benchmarks-gdb -test-benchmarks-gdb: - $(MAKE) -C $(TOY_BENCHMARKSDIR) gdb -k - #TODO: mustfail tests #util targets diff --git a/source/toy_table.c b/source/toy_table.c index 8309372..33b7991 100644 --- a/source/toy_table.c +++ b/source/toy_table.c @@ -46,7 +46,8 @@ static void probeAndInsert(Toy_Table** tableHandle, Toy_Value key, Toy_Value val } //adjust and continue - probe = (probe + 1) % (*tableHandle)->capacity; + probe++; + probe &= (*tableHandle)->capacity - 1; //DOOM hack entry.psl++; } } @@ -133,7 +134,8 @@ Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) { } //adjust and continue - probe = (probe + 1) % (*tableHandle)->capacity; + probe++; + probe &= (*tableHandle)->capacity - 1; //DOOM hack } } @@ -158,13 +160,15 @@ void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key) { } //adjust and continue - probe = (probe + 1) % (*tableHandle)->capacity; + probe++; + probe &= (*tableHandle)->capacity - 1; //DOOM hack } //shift along the later entries for (unsigned int i = (*tableHandle)->minPsl; i < (*tableHandle)->maxPsl; i++) { - unsigned int p = (probe + i + 0) % (*tableHandle)->capacity; //prev - unsigned int u = (probe + i + 1) % (*tableHandle)->capacity; //current + //DOOM hack used twice + unsigned int p = (probe + i + 0) & ((*tableHandle)->capacity-1); //prev + unsigned int u = (probe + i + 1) & ((*tableHandle)->capacity-1); //current (*tableHandle)->data[p] = (*tableHandle)->data[u]; (*tableHandle)->data[p].psl--; diff --git a/tests/benchmarks/array.c b/tests/benchmarks/array.c deleted file mode 100644 index b9a0207..0000000 --- a/tests/benchmarks/array.c +++ /dev/null @@ -1,26 +0,0 @@ -#include "toy_array.h" - -#include -#include - -int main(int argc, char* argv[]) { -// for (int i = 0; i < argc; i++) { -// printf("argv[%d]: %s\n", i, argv[i]); -// } - -// if (iargc != 2) return -1; - - unsigned int iterations = atoi(argv[1]); - -// printf("Found %d iterations\n", iterations); - - Toy_Array* array = TOY_ARRAY_ALLOCATE(); - - for (int i = 0; i < iterations; i++) { - TOY_ARRAY_PUSHBACK(array, TOY_VALUE_FROM_INTEGER(i)); - } - - TOY_ARRAY_FREE(array); - - return 0; -} diff --git a/tests/benchmarks/gdb_init b/tests/benchmarks/gdb_init index 31f1f7b..6c33498 100644 --- a/tests/benchmarks/gdb_init +++ b/tests/benchmarks/gdb_init @@ -1,3 +1,2 @@ set breakpoint pending on - diff --git a/tests/benchmarks/modulo_hack/bench_main.c b/tests/benchmarks/modulo_hack/bench_main.c new file mode 100644 index 0000000..12fb027 --- /dev/null +++ b/tests/benchmarks/modulo_hack/bench_main.c @@ -0,0 +1,60 @@ +#include "toy_table.h" + +#include + +//utils +unsigned int hashUInt(unsigned int x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +void stress_inserts(unsigned int seed, unsigned int iterations, unsigned int limit) { + //randomly generate a series of key-value pairs (from a seed) and insert them + { + //setup + Toy_Table* table = Toy_allocateTable(); + + for (unsigned int i = 0; i < iterations; i++) { + //next seed + seed = hashUInt(seed); + + //don't exceed a certain number of entries + unsigned int masked = seed & (limit-1); //lol + + //actual values don't matter, as long as they can be recreated + Toy_Value key = TOY_VALUE_FROM_INTEGER(masked); + Toy_Value value = TOY_VALUE_FROM_INTEGER(masked); + + Toy_insertTable(&table, key, value); + } + + //cleanup + Toy_freeTable(table); + } +} + +int main(int argc, char* argv[]) { + if (argc != 3) { + printf("Usage: %s iterations limit\n", argv[0]); + return 0; + } + + unsigned int iterations = 0; + unsigned int limit = 0; + + sscanf(argv[1], "%u", &iterations); + sscanf(argv[2], "%u", &limit); + + //limit to 16mb + if (limit * sizeof(Toy_TableEntry) > (1024 * 1024 * 16)) { + printf("Error: limit must be below %u for safety reasons\n", (1024 * 1024 * 16)/sizeof(Toy_TableEntry)); + return 0; + } + + //run the stress test + stress_inserts(42, iterations, limit); + + return 0; +} diff --git a/tests/benchmarks/modulo_hack/makefile b/tests/benchmarks/modulo_hack/makefile new file mode 100644 index 0000000..70fcd18 --- /dev/null +++ b/tests/benchmarks/modulo_hack/makefile @@ -0,0 +1,115 @@ +#compiler settings +CC=gcc +CFLAGS+=-std=c17 -g -Wall -Werror -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable -Wformat=2 +LIBS+=-lm +LDFLAGS+= + +ifeq ($(shell uname),Linux) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(OS),Windows_NT) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(shell uname),Darwin) +LDFLAGS=-Wl,-dead_strip +else + @echo "LDFLAGS set failed - what platform is this?" +endif + +#patched in +TOY_SOURCEDIR=source + +#directories +TEST_ROOTDIR=../../.. +TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) +TEST_CASESDIR=. + +TEST_OUTDIR=out +TEST_OBJDIR=obj + +#file names +TEST_SOURCEFILES=$(wildcard $(TEST_SOURCEDIR)/*.c) +TEST_CASESFILES=$(wildcard $(TEST_CASESDIR)/bench_*.c) + +#build the object files, compile the test cases, and run +all: clean + $(MAKE) build-source + $(MAKE) build-cases + $(MAKE) build-link + $(MAKE) build-run + +all-override: clean + $(MAKE) TEST_SOURCEFILES='$(subst $(TEST_SOURCEDIR)/$(OVERRIDE),$(OVERRIDE),$(TEST_SOURCEFILES))' build-source-override + $(MAKE) build-cases + $(MAKE) build-link + $(MAKE) build-run + +.PHONY: build-source-override +build-source-override: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_SOURCEFILES:.c=.o))) + $(CC) -c -o $(TEST_OBJDIR)/$(OVERRIDE:.c=.o) $(OVERRIDE) $(addprefix -I,$(TEST_SOURCEDIR)) $(CFLAGS) -fdata-sections -ffunction-sections + +#targets for each step +.PHONY: build-source +build-source: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_SOURCEFILES:.c=.o))) + +.PHONY: build-cases +build-cases: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_CASESFILES:.c=.o))) + +.PHONY: build-link +build-link: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) + +.PHONY: build-run +build-run: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.run))) + +#compilation steps +$(TEST_OBJDIR)/%.o: $(TEST_SOURCEDIR)/%.c + $(CC) -c -o $@ $< $(addprefix -I,$(TEST_SOURCEDIR)) $(CFLAGS) -fdata-sections -ffunction-sections + +$(TEST_OBJDIR)/%.o: $(TEST_CASESDIR)/%.c + $(CC) -c -o $@ $< $(addprefix -I,$(TEST_SOURCEDIR) $(TEST_CASESDIR)) $(CFLAGS) -fdata-sections -ffunction-sections + +$(TEST_OUTDIR)/%.exe: $(TEST_OBJDIR)/%.o + @$(CC) -o $@ $< $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_SOURCEFILES:.c=.o))) $(CFLAGS) $(LIBS) $(LDFLAGS) + +.PRECIOUS: $(TEST_OUTDIR)/%.run +$(TEST_OUTDIR)/%.run: $(TEST_OUTDIR)/%.exe + @/usr/bin/time --format "%C; $(OVERRIDE)\nUser System\n%U %E" $< 100000000 512 + @/usr/bin/time --format "%C; $(OVERRIDE)\nUser System\n%U %E" $< 100000000 1024 + @/usr/bin/time --format "%C; $(OVERRIDE)\nUser System\n%U %E" $< 100000000 4096 + +#util targets +$(TEST_OUTDIR): + mkdir $(TEST_OUTDIR) + +$(TEST_OBJDIR): + mkdir $(TEST_OBJDIR) + +#util commands +.PHONY: clean +clean: +ifeq ($(shell uname),Linux) + find . -type f -name '*.o' -delete + find . -type f -name '*.a' -delete + find . -type f -name '*.exe' -delete + find . -type f -name '*.dll' -delete + find . -type f -name '*.lib' -delete + find . -type f -name '*.so' -delete + find . -type f -name '*.dylib' -delete + find . -type d -name 'out' -delete + find . -type d -name 'obj' -delete +else ifeq ($(OS),Windows_NT) + $(RM) *.o *.a *.exe *.dll *.lib *.so *.dylib + $(RM) out + $(RM) obj +else ifeq ($(shell uname),Darwin) + find . -type f -name '*.o' -delete + find . -type f -name '*.a' -delete + find . -type f -name '*.exe' -delete + find . -type f -name '*.dll' -delete + find . -type f -name '*.lib' -delete + find . -type f -name '*.so' -delete + find . -type f -name '*.dylib' -delete + find . -type d -name 'out' -delete + find . -type d -name 'obj' -delete +else + @echo "Deletion failed - what platform is this?" +endif +