Skip to content

Commit

Permalink
Benchmarked memory models for Toy_Array, read more
Browse files Browse the repository at this point in the history
The results can be found in
'tests/benchmarks/array_allocation/results.md'

The results are disappointing, as 'malloc()' is simply faster in every
possible situation compared to my custom arena allocator.
  • Loading branch information
Ratstail91 committed Dec 23, 2024
1 parent 8b5cc3b commit 223db84
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 5 deletions.
6 changes: 3 additions & 3 deletions source/toy_bucket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions tests/benchmarks/array_allocation/bench_main.c
Original file line number Diff line number Diff line change
@@ -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;
}
104 changes: 104 additions & 0 deletions tests/benchmarks/array_allocation/makefile
Original file line number Diff line number Diff line change
@@ -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

48 changes: 48 additions & 0 deletions tests/benchmarks/array_allocation/results.md
Original file line number Diff line number Diff line change
@@ -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
```
63 changes: 63 additions & 0 deletions tests/benchmarks/array_allocation/toy_array_bucket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include "toy_array.h"
#include "toy_console_colors.h"

#include "toy_bucket.h"

#include <string.h>

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;
```
*/
34 changes: 34 additions & 0 deletions tests/benchmarks/array_allocation/toy_array_malloc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "toy_array.h"
#include "toy_console_colors.h"

#include <stdio.h>
#include <stdlib.h>

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;
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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=.

Expand Down

0 comments on commit 223db84

Please sign in to comment.