Feature: Use arena-allocation for memory handling

Arena allocation make deallocation of nested structed MUCH faster, and
improves the spatial locality of allocations. It makes no sense to
deallocate only parts of a JSON structure so arenas are a good fit here.
This commit is contained in:
olemorud
2023-05-16 02:44:49 +02:00
committed by Ole Kristian Morud
parent f58c5dcfee
commit b3821cf8bf
11 changed files with 124 additions and 97 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "arena-allocator"]
path = arena-allocator
url = https://github.com/olemorud/arena-allocator.git

View File

@@ -10,15 +10,18 @@ COMPILER ?= gcc
CC := gcc CC := gcc
# -fsanitize={address,undefined} # -fsanitize={address,undefined}
CFLAGS.gcc.debug := -Og -ggdb -fanalyzer -DBACKTRACE -rdynamic -fsanitize=address -fno-omit-frame-pointer CFLAGS.gcc.debug := -Og -ggdb -DBACKTRACE -rdynamic -fanalyzer -fsanitize=address -fno-omit-frame-pointer
CFLAGS.gcc.release := -O3 -g -march=native -DNDEBUG CFLAGS.gcc.release := -O3 -g -march=native -DNDEBUG
CFLAGS.gcc := ${CFLAGS.gcc.${BUILD}} -Iinclude -Wall -Wextra -Wpedantic -Werror -Wno-strict-aliasing -fstack-protector-all -std=gnu11 CFLAGS.gcc := ${CFLAGS.gcc.${BUILD}} -fstack-protector-all -std=gnu11 #-Wpedantic
CFLAGS.clang.debug=-O0 -g3 -DBACKTRACE -rdynamic CFLAGS.clang.debug=-O0 -g3 -DBACKTRACE -rdynamic
CFLAGS.clang.release=-O3 -g -march=native -DNDEBUG CFLAGS.clang.release=-O3 -g -march=native -DNDEBUG
CFLAGS.clang=-Iinclude -Wall -Wextra -Wpedantic -Werror -Wno-strict-aliasing -fstack-protector-all ${CFLAGS.clang.${BUILD}} CFLAGS.clang=-Iinclude -Wno-strict-aliasing -fstack-protector-all ${CFLAGS.clang.${BUILD}}
CFLAGS := ${CFLAGS.${COMPILER}} CFLAGS := ${CFLAGS.${COMPILER}} \
-Iinclude -Iarena-allocator/include \
-std=c2x \
-Wall -Wextra -Wpedantic -Werror
LD_PRELOAD:= LD_PRELOAD:=
@@ -27,18 +30,24 @@ LD_PRELOAD:=
BUILD_DIR := bin/${BUILD} BUILD_DIR := bin/${BUILD}
OBJ_DIR := .obj/${BUILD} OBJ_DIR := .obj/${BUILD}
_OBJS := main.o parse.o json_value.o util.o _OBJS := main.o parse.o json_value.o util.o libarena.a
OBJS := $(patsubst %,$(OBJ_DIR)/%,$(_OBJS)) OBJS := $(patsubst %,$(OBJ_DIR)/%,$(_OBJS))
all : $(BUILD_DIR)/parse all : $(BUILD_DIR)/parse
$(BUILD_DIR)/parse : $(OBJS) | $(BUILD_DIR) Makefile $(BUILD_DIR)/parse : $(OBJS) $(OBJ_DIR)/libarena.a | $(BUILD_DIR) Makefile
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@
$(OBJ_DIR)/main.o : src/main.c | $(OBJ_DIR) Makefile $(OBJ_DIR)/main.o : src/main.c arena-allocator | $(OBJ_DIR) Makefile
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -c $< -o $@ $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -c $< -o $@
$(OBJ_DIR)/libarena.a : arena-allocator .gitmodules $(wildcard .git/modules/arena-allocator/HEAD) | $(OBJ_DIR) Makefile
(cd arena-allocator && make static)
cp --force arena-allocator/lib/libarena.a $(OBJ_DIR)/libarena.a
arena-allocator : .gitmodules
git submodule update $@
$(OBJ_DIR)/%.o : src/%.c include/%.h | $(OBJ_DIR) Makefile $(OBJ_DIR)/%.o : src/%.c include/%.h | $(OBJ_DIR) Makefile
$(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -c $< -o $@ $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -c $< -o $@

1
arena-allocator Submodule

Submodule arena-allocator added at f8e6e2e6d2

View File

@@ -1,4 +1,5 @@
-Iinclude -Iinclude
-Iarena-allocator/include
-Wall -Wall
-Werror -Werror
-Wpedantic -Wpedantic

View File

@@ -2,6 +2,7 @@
#ifndef _JSON_VALUE_H #ifndef _JSON_VALUE_H
#define _JSON_VALUE_H #define _JSON_VALUE_H
#include "arena.h"
#include "config.h" #include "config.h"
#include <stdbool.h> // bool #include <stdbool.h> // bool
@@ -9,7 +10,7 @@ typedef struct obj_entry {
char const* key; char const* key;
struct json_value* val; struct json_value* val;
struct obj_entry* next; struct obj_entry* next;
} * __p_obj_entry; }* __p_obj_entry;
typedef __p_obj_entry obj_t[OBJ_SIZE]; typedef __p_obj_entry obj_t[OBJ_SIZE];
@@ -32,11 +33,11 @@ struct json_value {
}; };
void* obj_at(obj_t m, char* const key); void* obj_at(obj_t m, char* const key);
bool obj_insert(obj_t m, char* const key, struct json_value* value); bool obj_insert(obj_t m, char* const key, struct json_value* value, arena_t* arena);
void obj_delete(obj_t* m); void obj_delete(obj_t* m, arena_t* arena);
void print_json(struct json_value val, int indent); void print_json(struct json_value val, int indent);
void json_value_delete(struct json_value val); void json_value_delete(struct json_value val, arena_t* arena);
#endif #endif

View File

@@ -4,8 +4,9 @@
#include "json_value.h" #include "json_value.h"
#include "arena.h"
#include <stdio.h> // FILE* #include <stdio.h> // FILE*
struct json_value parse_json_value(FILE* fp); struct json_value parse_json_value(FILE* fp, arena_t* arena);
#endif #endif

View File

@@ -10,6 +10,6 @@ __attribute__((__noreturn__)) void err_ctx(int exit_code, FILE* fp, const char*
void* malloc_or_die(size_t size); void* malloc_or_die(size_t size);
void* realloc_or_die(void* ptr, size_t size); void* realloc_or_die(void* ptr, size_t size);
void* calloc_or_die(size_t nmemb, size_t size); void* calloc_or_die(size_t nmemb, size_t size);
void print_trace(); void print_trace(void);
#endif #endif

View File

@@ -23,13 +23,11 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "arena.h"
#include "json_value.h" #include "json_value.h"
#include "util.h" #include "util.h"
bool obj_insert(obj_t m, char* const key, struct json_value* value);
size_t obj_hash(char const* str); size_t obj_hash(char const* str);
void obj_delete(obj_t* m);
void* obj_at(obj_t m, char* const key);
void print_array(struct json_value** arr, int cur_indent, int indent_amount); void print_array(struct json_value** arr, int cur_indent, int indent_amount);
void print_json_value(struct json_value val, int cur_indent, int indent_amount); void print_json_value(struct json_value val, int cur_indent, int indent_amount);
@@ -95,7 +93,7 @@ void* obj_at(obj_t m, char* const key)
returns true if successful returns true if successful
returns false if key already exists returns false if key already exists
*/ */
bool obj_insert(obj_t m, char* const key, struct json_value* value) bool obj_insert(obj_t m, char* const key, struct json_value* value, arena_t* arena)
{ {
size_t i = obj_hash(key); size_t i = obj_hash(key);
struct obj_entry* cur = m[i]; struct obj_entry* cur = m[i];
@@ -119,7 +117,7 @@ bool obj_insert(obj_t m, char* const key, struct json_value* value)
return false; return false;
/* populate new entry */ /* populate new entry */
cur = malloc_or_die(sizeof(struct obj_entry)); cur = arena_alloc(arena, sizeof *cur);
cur->key = key; cur->key = key;
cur->val = value; cur->val = value;
cur->next = m[i]; cur->next = m[i];
@@ -133,24 +131,24 @@ bool obj_insert(obj_t m, char* const key, struct json_value* value)
/* /*
Free memory allocated for json_value val Free memory allocated for json_value val
*/ */
void json_value_delete(struct json_value val) void json_value_delete(struct json_value val, arena_t* arena)
{ {
switch (val.type) { switch (val.type) {
case array: case array:
for (size_t i = 0; val.array[i] != NULL; i++) { for (size_t i = 0; val.array[i] != NULL; i++) {
json_value_delete(*(val.array[i])); json_value_delete(*(val.array[i]), arena);
free(val.array[i]); arena_free(arena, val.array[i]);
} }
free(val.array); arena_free(arena, val.array);
break; break;
case object: case object:
obj_delete(val.object); obj_delete(val.object, arena);
break; break;
case string: case string:
free(val.string); arena_free(arena, val.string);
break; break;
default: default:
@@ -159,26 +157,27 @@ void json_value_delete(struct json_value val)
} }
/* /*
Free memory allocated for obj Recursively deletes object and its children
Recursively deletes children objects Included for completeness. To efficiently delete an object, reset
its associated arena instead.
*/ */
void obj_delete(obj_t* m) void obj_delete(obj_t* m, arena_t* arena)
{ {
for (size_t i = 0; i < OBJ_SIZE; i++) { for (size_t i = 0; i < OBJ_SIZE; i++) {
struct obj_entry *e = (*m)[i], *tmp; struct obj_entry *e = (*m)[i], *tmp;
while (e != NULL) { while (e != NULL) {
json_value_delete(*(e->val)); json_value_delete(*(e->val), arena);
free((char*)e->key); arena_free(arena, (char*)e->key);
free(e->val); arena_free(arena, e->val);
tmp = e; tmp = e;
e = e->next; e = e->next;
free(tmp); arena_free(arena, tmp);
} }
} }
free(m); arena_free(arena, m);
} }
void add_indent(int n) void add_indent(int n)

View File

@@ -26,6 +26,8 @@
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
arena_t arena_default = arena_new();
if (argc != 2) { if (argc != 2) {
errx(EXIT_FAILURE, "Usage: %s <file>", argv[0]); errx(EXIT_FAILURE, "Usage: %s <file>", argv[0]);
} }
@@ -34,13 +36,13 @@ int main(int argc, char* argv[])
FILE* fp = fopen(argv[1], "r"); FILE* fp = fopen(argv[1], "r");
struct json_value x = parse_json_value(fp); struct json_value x = parse_json_value(fp, &arena_default);
print_json(x, 1); print_json(x, 1);
json_value_delete(x);
fclose(fp); fclose(fp);
arena_delete(&arena_default);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@@ -20,24 +20,37 @@
#include "parse.h" #include "parse.h"
#include "arena.h"
#include "config.h" #include "config.h"
#include "json_value.h" #include "json_value.h"
#include "util.h" #include "util.h"
#include <ctype.h> // isspace, isdigit #include <ctype.h> // isspace, isdigit
#include <err.h> // err, errx #include <err.h> // err, errx
#include <math.h> // NAN
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> // exit, EXIT_SUCCESS, EXIT_FAILURE #include <stdlib.h> // exit, EXIT_SUCCESS, EXIT_FAILURE
#include <string.h> // strcmp #include <string.h> // strcmp
char* read_string(FILE* fp);
obj_t* read_object(FILE* fp);
void discard_whitespace(FILE* fp);
bool read_boolean(FILE* fp); bool read_boolean(FILE* fp);
void read_null(FILE* fp); char* read_string(FILE* fp, arena_t* arena);
double read_number(FILE* fp); double read_number(FILE* fp);
struct json_value** read_array(FILE* fp); obj_t* read_object(FILE* fp, arena_t* arena);
struct json_value** read_array(FILE* fp, arena_t* arena);
void discard_whitespace(FILE* fp); void discard_whitespace(FILE* fp);
void read_null(FILE* fp);
/*
ghetto way to hunt down bugs
#ifdef __GNUC__
#define _fgetc(fp) \
({int retval; retval = fgetc(fp); fputc(retval, stderr); retval;})
#else
#define _fgetc(fp) \
fgetc(fp)
#endif
*/
#define _fgetc(fp) fgetc(fp)
/* /*
Consumes the next whitespace character in a file stream Consumes the next whitespace character in a file stream
@@ -47,7 +60,7 @@ void discard_whitespace(FILE* fp)
{ {
int c; int c;
while (isspace(c = fgetc(fp))) { while (isspace(c = _fgetc(fp))) {
if (c == EOF) if (c == EOF)
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
} }
@@ -64,10 +77,10 @@ void discard_whitespace(FILE* fp)
These structures can be nested. These structures can be nested.
*/ */
struct json_value parse_json_value(FILE* fp) struct json_value parse_json_value(FILE* fp, arena_t* arena)
{ {
discard_whitespace(fp); discard_whitespace(fp);
int c = fgetc(fp); int c = _fgetc(fp);
struct json_value result = { 0 }; struct json_value result = { 0 };
@@ -77,17 +90,17 @@ struct json_value parse_json_value(FILE* fp)
case '{': case '{':
result.type = object; result.type = object;
result.object = read_object(fp); result.object = read_object(fp, arena);
break; break;
case '"': case '"':
result.type = string; result.type = string;
result.string = read_string(fp); result.string = read_string(fp, arena);
break; break;
case '[': case '[':
result.type = array; result.type = array;
result.array = read_array(fp); result.array = read_array(fp, arena);
break; break;
case 't': case 't':
@@ -127,21 +140,26 @@ struct json_value parse_json_value(FILE* fp)
Consumes a JSON string from a file stream and returns a corresponding char* Consumes a JSON string from a file stream and returns a corresponding char*
*/ */
char* read_string(FILE* fp) char* read_string(FILE* fp, arena_t* arena)
{ {
int c; int c;
size_t i = 0, result_size = 16; size_t i = 0, result_size = 16;
char* result = malloc_or_die(result_size); // char* result = malloc_or_die(result_size);
char* result = arena_alloc(arena, result_size);
bool escaped = false; bool escaped = false;
while (true) { while (true) {
if (i + 1 >= result_size) { if (i + 1 >= result_size) {
result_size *= 2; result_size *= 2;
result = realloc_or_die(result, result_size); result = arena_realloc_tail(arena, result_size);
if (result == NULL)
err_ctx(EXIT_FAILURE, fp, "could not allocate memory for string"
"%s",
__func__);
} }
c = fgetc(fp); c = _fgetc(fp);
if (escaped) { if (escaped) {
escaped = false; escaped = false;
@@ -155,10 +173,9 @@ char* read_string(FILE* fp)
case '"': case '"':
result[i++] = '\0'; result[i++] = '\0';
return realloc_or_die(result, i); return arena_realloc_tail(arena, i);
case EOF: case EOF:
free(result);
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
default: default:
@@ -177,26 +194,25 @@ char* read_string(FILE* fp)
Consumes a JSON object from a file stream and returns a corresponding obj_t Consumes a JSON object from a file stream and returns a corresponding obj_t
*/ */
obj_t* read_object(FILE* fp) obj_t* read_object(FILE* fp, arena_t* arena)
{ {
obj_t* result = calloc_or_die(1, sizeof(obj_t)); // obj_t* result = calloc_or_die(1, sizeof(obj_t));
obj_t* result = arena_calloc(arena, 1, sizeof *result);
char* key; char* key;
while (true) { while (true) {
/* read key */ /* read key */
discard_whitespace(fp); discard_whitespace(fp);
switch (fgetc(fp)) { switch (_fgetc(fp)) {
case EOF: case EOF:
free(result);
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
default: default:
free(result);
err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected \"", __func__); err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected \"", __func__);
case '"': case '"':
key = read_string(fp); key = read_string(fp, arena);
break; break;
case '}': case '}':
@@ -206,39 +222,35 @@ obj_t* read_object(FILE* fp)
/* check for ':' separator */ /* check for ':' separator */
discard_whitespace(fp); discard_whitespace(fp);
switch (fgetc(fp)) { switch (_fgetc(fp)) {
case ':': case ':':
break; break;
case EOF: case EOF:
free(result);
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
default: default:
free(result);
err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected ':'", __func__); err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected ':'", __func__);
} }
/* read value */ /* read value */
discard_whitespace(fp); discard_whitespace(fp);
struct json_value* val = calloc_or_die(1, sizeof(struct json_value)); // struct json_value* val = calloc_or_die(1, sizeof(struct json_value));
*val = parse_json_value(fp); struct json_value* val = arena_calloc(arena, 1, sizeof *val);
*val = parse_json_value(fp, arena);
/* insert key-value pair to obj */ /* insert key-value pair to obj */
if (!obj_insert(*result, key, val)) { if (!obj_insert(*result, key, val, arena)) {
free(result); fprintf(stderr, "failed to insert pair (%s, %p)\n", key, (void*)val);
free(val); exit(EXIT_FAILURE);
errx(EXIT_FAILURE, "failed to insert pair (%s, %p)", key, (void*)val);
} }
/* read separator or end of object */ /* read separator or end of object */
discard_whitespace(fp); discard_whitespace(fp);
switch (fgetc(fp)) { switch (_fgetc(fp)) {
case EOF: case EOF:
free(val);
free(result);
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
case ',': case ',':
@@ -248,8 +260,6 @@ obj_t* read_object(FILE* fp)
return result; return result;
default: default:
free(val);
free(result);
err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected ',' or '}'", __func__); err_ctx(UNEXPECTED_CHAR, fp, "(%s) expected ',' or '}'", __func__);
} }
} }
@@ -264,42 +274,43 @@ obj_t* read_object(FILE* fp)
Consumes a JSON array from a file stream and returns Consumes a JSON array from a file stream and returns
a NULL separated array of json_value pointers a NULL separated array of json_value pointers
*/ */
struct json_value** read_array(FILE* fp) struct json_value** read_array(FILE* fp, arena_t* arena)
{ {
int c; int c;
size_t i = 0, output_size = 16; size_t i = 0, output_size = 16;
struct json_value** output = malloc_or_die(output_size * sizeof(struct json_value*)); struct json_value** output = malloc_or_die(output_size * sizeof *output);
// struct json_value** output = arena_alloc(arena, output_size * sizeof *output);
while (true) { while (true) {
if (i + 1 >= output_size) { if (i >= output_size) {
output_size *= 2; output_size *= 2;
output = realloc_or_die(output, output_size * sizeof(struct json_value*)); // output = arena_realloc_tail(arena, output_size * sizeof *output);
output = realloc_or_die(output, output_size * sizeof *output);
} }
discard_whitespace(fp); discard_whitespace(fp);
c = fgetc(fp); c = _fgetc(fp);
switch (c) { switch (c) {
case EOF: case EOF:
free(output);
for (size_t j = 0; j < i; j++)
free(output[j]);
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);
break; break;
case ']': case ']':
output[i++] = NULL; output[i++] = NULL;
return realloc_or_die(output, i * sizeof(struct json_value*)); struct json_value** x = arena_alloc(arena, i * sizeof *output);
memcpy(x, output, i * sizeof *output);
free(output);
return x;
case ',': case ',':
continue; continue;
default: default:
ungetc(c, fp); ungetc(c, fp);
output[i] = malloc_or_die(sizeof(struct json_value)); // output[i] = malloc_or_die(sizeof(struct json_value));
*output[i] = parse_json_value(fp); output[i] = arena_alloc(arena, sizeof *(output[i]));
*output[i] = parse_json_value(fp, arena);
i++; i++;
break; break;
} }
@@ -365,15 +376,12 @@ bool read_boolean(FILE* fp)
*/ */
double read_number(FILE* fp) double read_number(FILE* fp)
{ {
static const unsigned long neg_nan = 0xFFFFFFFFFFFFFFFFULL; double n = NAN;
double n = *(double*)&neg_nan;
int n_read = fscanf(fp, "%lf", &n); int n_read = fscanf(fp, "%lf", &n);
/* try to read as long instead */ if (n_read == 0)
if (n_read == 0) {
err_ctx(UNEXPECTED_CHAR, fp, "(%s) number expected, found %lf", __func__, n); err_ctx(UNEXPECTED_CHAR, fp, "(%s) number expected, found %lf", __func__, n);
}
if (n_read == EOF) if (n_read == EOF)
err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__); err_ctx(EARLY_EOF, fp, "(%s) unexpected EOF", __func__);

View File

@@ -42,13 +42,12 @@ void* realloc_or_die(void* ptr, size_t size)
{ {
void* tmp = realloc(ptr, size); void* tmp = realloc(ptr, size);
if (ptr == NULL) { if (tmp == NULL) {
if (errno != 0) { if (errno != 0) {
free(ptr);
err(errno, "realloc_or_die failed"); err(errno, "realloc_or_die failed");
} }
fprintf(stderr, "\nrealloc_or_die returned NULL but errno is 0, ptr: %p size: %zu\n", ptr, size); fprintf(stderr, "\nrealloc_or_die returned NULL but errno is 0, ptr: %p size: %zu\n", tmp, size);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -67,9 +66,11 @@ void* calloc_or_die(size_t nmemb, size_t size)
// from the glibc man pages // from the glibc man pages
// https://www.gnu.org/software/libc/manual/html_node/Backtraces.html // https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
void print_trace() void print_trace(void)
{ {
#ifdef BACKTRACE #ifndef BACKTRACE
return;
#else
void* array[500]; void* array[500];
char** strings; char** strings;
int size, i; int size, i;
@@ -79,8 +80,9 @@ void print_trace()
if (strings != NULL) { if (strings != NULL) {
printf("\n\n=== Obtained %d stack frames. === \n", size); printf("\n\n=== Obtained %d stack frames. === \n", size);
for (i = 0; i < size; i++) for (i = 0; i < size; i++) {
printf("%s\n", strings[i]); printf("%s\n", strings[i]);
}
} }
free(strings); free(strings);