diff --git a/source/toy_bucket.h b/source/toy_bucket.h index 8e42bdf..1da9700 100644 --- a/source/toy_bucket.h +++ b/source/toy_bucket.h @@ -2,10 +2,10 @@ #include "toy_common.h" -//NOTE: this structure has restrictions on it's usage: +//NOTE: this is an 'arena allocator', and has restrictions on it's usage: // - It can only expand until it is freed -// - It cannot be copied around within RAM -// - It cannot allocate more memory than it has capacity +// - It cannot be copied or moved around in memory +// - It cannot allocate more memory than it has 'capacity' // If each of these rules are followed, this is actually more efficient than other options //a custom allocator diff --git a/tests/benchmarks/array_allocation/bench_main.c b/tests/benchmarks/array_allocation/bench_main.c new file mode 100644 index 0000000..32d1950 --- /dev/null +++ b/tests/benchmarks/array_allocation/bench_main.c @@ -0,0 +1,40 @@ +#include "toy.h" + +//util macros +#define TOY_ARRAY_EXPAND(array) ((array) = ((array) != NULL && (array)->count + 1 > (array)->capacity ? Toy_resizeArray((array), (array)->capacity * TOY_ARRAY_EXPANSION_RATE) : (array))) +#define TOY_ARRAY_PUSHBACK(array, value) (TOY_ARRAY_EXPAND(array), (array)->data[(array)->count++] = (value)) + +void stress_fillArray(Toy_Array** array) { + //Toy_Value is either 8 or 16 bytes + for (int i = 0; i < 10 * 1000 * 1000; i++) { + TOY_ARRAY_PUSHBACK(*array, TOY_VALUE_FROM_INTEGER(i)); + } +} + +int main() { + //Compare different memory strategies for Toy_Array + + for (int i = 0; i < 100; i++) { + /* + + //malloc + Toy_Array* array = Toy_resizeArray(NULL, TOY_ARRAY_INITIAL_CAPACITY); + stress_fillArray(&array); + Toy_resizeArray(array, 0); + + /*/ + + //Toy_Bucket + benchBucket = Toy_allocateBucket(1024 * 1024 * 200); //200MB + + Toy_Array* array = Toy_resizeArray(NULL, TOY_ARRAY_INITIAL_CAPACITY); + stress_fillArray(&array); + Toy_resizeArray(array, 0); + + Toy_freeBucket(&benchBucket); + + //*/ + } + + return 0; +} diff --git a/tests/benchmarks/array_allocation/makefile b/tests/benchmarks/array_allocation/makefile new file mode 100644 index 0000000..2c71009 --- /dev/null +++ b/tests/benchmarks/array_allocation/makefile @@ -0,0 +1,104 @@ +#compiler settings +CC=gcc +CFLAGS+=-std=c17 -g -Wall -Werror -Wextra -Wpedantic -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 incl +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 + +#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) + strip $@ + +.PRECIOUS: $(TEST_OUTDIR)/%.run +$(TEST_OUTDIR)/%.run: $(TEST_OUTDIR)/%.exe + @/usr/bin/time --format "User System\n%U %E" $< + +#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 + diff --git a/tests/benchmarks/array_allocation/results.md b/tests/benchmarks/array_allocation/results.md new file mode 100644 index 0000000..af405be --- /dev/null +++ b/tests/benchmarks/array_allocation/results.md @@ -0,0 +1,48 @@ + +These tests compared two memory solutions for `Toy_Array`, under different conditions. 'Pushes' is the number of iterations used in the following stress function: + +```c +//defined in toy_value.h +#define TOY_VALUE_FROM_INTEGER(value) ((Toy_Value){{ .integer = value }, TOY_VALUE_INTEGER}) + +//util macros +#define TOY_ARRAY_EXPAND(array) ((array) = ((array) != NULL && (array)->count + 1 > (array)->capacity ? Toy_resizeArray((array), (array)->capacity * TOY_ARRAY_EXPANSION_RATE) : (array))) +#define TOY_ARRAY_PUSHBACK(array, value) (TOY_ARRAY_EXPAND(array), (array)->data[(array)->count++] = (value)) + +void stress_fillArray(Toy_Array** array) { + //Toy_Value is either 8 or 16 bytes + for (int i = 0; i < 10 * 1000 * 1000; i++) { + TOY_ARRAY_PUSHBACK(*array, TOY_VALUE_FROM_INTEGER(i)); + } +} +``` + +'Memory' is the capacity of the `Toy_Bucket` when used. In the first set of results, the stress function was called once, while the second set called it 100 times, clearing the memory entirely each time. 'malloc' and 'bucket' shows the measured time taken for each situation. + +``` +1x run + +pushes: 1000 * 1000 +memory: 1024 * 1024 * 20 +malloc: 0.01 0:00.01 +bucket: 0.00 0:00.02 + +pushes: 10 * 1000 * 1000 +memory: 1024 * 1024 * 200 +malloc: 0.08 0:00.14 +bucket: 0.13 0:00.29 +``` + +``` +100x looped runs + +pushes: 1000 * 1000 +memory: 1024 * 1024 * 20 +malloc: 0.94 0:01.47 +bucket: 1.02 0:02.60 + +pushes: 10 * 1000 * 1000 +memory: 1024 * 1024 * 200 +malloc: 8.28 0:15.77 +bucket: 11.81 0:30.06 +``` \ No newline at end of file diff --git a/tests/benchmarks/array_allocation/toy_array_bucket.c b/tests/benchmarks/array_allocation/toy_array_bucket.c new file mode 100644 index 0000000..0fb8b87 --- /dev/null +++ b/tests/benchmarks/array_allocation/toy_array_bucket.c @@ -0,0 +1,63 @@ +#include "toy_array.h" +#include "toy_console_colors.h" + +#include "toy_bucket.h" + +#include + +Toy_Bucket* benchBucket = NULL; + +Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) { + //allow the array to be 'lost', and freed with the bucket + if (capacity == 0) { + return NULL; + } + + //initial allocation + if (paramArray == NULL) { + Toy_Array* array = Toy_partitionBucket(&benchBucket, capacity * sizeof(Toy_Value) + sizeof(Toy_Array)); + + array->capacity = capacity; + array->count = 0; + + return array; + } + + //if your array is growing, partition more space, then copy over the data + if (paramArray->capacity < capacity) { + Toy_Array* array = Toy_partitionBucket(&benchBucket, capacity * sizeof(Toy_Value) + sizeof(Toy_Array)); + + memcpy(array, paramArray, paramArray->count * sizeof(Toy_Value) + sizeof(Toy_Array)); //doesn't copy any blank space + + array->capacity = capacity; + array->count = paramArray->count; + return array; + } + + //if some values will be removed, free them first, then return the result + if (paramArray->count > capacity) { + for (unsigned int i = capacity; i < paramArray->count; i++) { + Toy_freeValue(paramArray->data[i]); + } + + paramArray->capacity = capacity; //don't worry about another allocation, this is faster + paramArray->count = capacity; + + return paramArray; + } + + //unreachable + return paramArray; +} + +/* + +Note: This needs to be pasted in the header: + +``` +struct Toy_Bucket; + +extern struct Toy_Bucket* benchBucket; +``` + +*/ \ No newline at end of file diff --git a/tests/benchmarks/array_allocation/toy_array_malloc.c b/tests/benchmarks/array_allocation/toy_array_malloc.c new file mode 100644 index 0000000..199d742 --- /dev/null +++ b/tests/benchmarks/array_allocation/toy_array_malloc.c @@ -0,0 +1,34 @@ +#include "toy_array.h" +#include "toy_console_colors.h" + +#include +#include + +Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) { + if (capacity == 0) { + free(paramArray); + return NULL; + } + + //if some values will be removed, free them first + if (paramArray != NULL && paramArray->count > capacity) { + for (unsigned int i = capacity; i < paramArray->count; i++) { + Toy_freeValue(paramArray->data[i]); + } + } + + unsigned int originalCapacity = paramArray == NULL ? 0 : paramArray->capacity; + + Toy_Array* array = realloc(paramArray, capacity * sizeof(Toy_Value) + sizeof(Toy_Array)); + + if (array == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to resize a 'Toy_Array' from %d to %d capacity\n" TOY_CC_RESET, (int)originalCapacity, (int)capacity); + exit(-1); + } + + array->capacity = capacity; + array->count = paramArray == NULL ? 0 : + (array->count > capacity ? capacity : array->count); //truncate lost data + + return array; +} \ No newline at end of file diff --git a/tests/benchmarks/bench_main.c b/tests/benchmarks/bucket_ideal/bench_main.c similarity index 100% rename from tests/benchmarks/bench_main.c rename to tests/benchmarks/bucket_ideal/bench_main.c diff --git a/tests/benchmarks/makefile b/tests/benchmarks/bucket_ideal/makefile similarity index 98% rename from tests/benchmarks/makefile rename to tests/benchmarks/bucket_ideal/makefile index 6060718..c8a31f6 100644 --- a/tests/benchmarks/makefile +++ b/tests/benchmarks/bucket_ideal/makefile @@ -14,11 +14,11 @@ else @echo "LDFLAGS set failed - what platform is this?" endif -#patched in +#patched incl TOY_SOURCEDIR=source #directories -TEST_ROOTDIR=../.. +TEST_ROOTDIR=../../.. TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) TEST_CASESDIR=.