Skip to content

Commit

Permalink
Add json_object_array_shrink() (and array_list_shrink()) and use it i…
Browse files Browse the repository at this point in the history
…n json_tokener to minimize the amount of memory used. This results in a 39%-50% reduction in memory use (peak RSS, peak heap usage) on the jc-bench benchmark and 9% shorter runtime.

Also add the json_object_new_array_ext, array_list_new2, and array_list_shrink functions.
  • Loading branch information
hawicz committed Jun 20, 2020
1 parent 99bb212 commit e26a119
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 3 deletions.
8 changes: 7 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Next Release 0.15

Deprecated and removed features:
--------------------------------
...none yet...
* array_list_new() has been deprecated in favor of array_list_new2()

Other changes
--------------
Expand All @@ -19,6 +19,12 @@ Other changes
less memory usage.
Memory used just for json_object structures decreased 27%, so use cases
with fewer arrays and/or strings would benefit more.
* Minimize memory usage in array handling in json_tokener by shrinking
arrays to the exact number of elements parsed. On bench/ benchmark:
9% faster test time, 39%(max RSS)-50%(peak heap) less memory usage.
Add json_object_array_shrink() and array_list_shrink() functions.
* Add json_object_new_array_ext(int) and array_list_new_2(int) to allow
arrays to be allocated with the exact size needed, when known.


***
Expand Down
29 changes: 28 additions & 1 deletion arraylist.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@
#include "arraylist.h"

struct array_list *array_list_new(array_list_free_fn *free_fn)
{
return array_list_new2(free_fn, ARRAY_LIST_DEFAULT_SIZE);
}

struct array_list *array_list_new2(array_list_free_fn *free_fn, int initial_size)
{
struct array_list *arr;

arr = (struct array_list *)malloc(sizeof(struct array_list));
if (!arr)
return NULL;
arr->size = ARRAY_LIST_DEFAULT_SIZE;
arr->size = initial_size;
arr->length = 0;
arr->free_fn = free_fn;
if (!(arr->array = (void **)malloc(arr->size * sizeof(void *))))
Expand Down Expand Up @@ -96,6 +101,26 @@ static int array_list_expand_internal(struct array_list *arr, size_t max)
return 0;
}

int array_list_shrink(struct array_list *arr, size_t empty_slots)
{
void *t;
size_t new_size;

new_size = arr->length + empty_slots;
if (new_size == arr->size)
return 0;
if (new_size > arr->size)
return array_list_expand_internal(arr, new_size);
if (new_size == 0)
new_size = 1;

if (!(t = realloc(arr->array, new_size * sizeof(void *))))
return -1;
arr->array = (void **)t;
arr->size = new_size;
return 0;
}

//static inline int _array_list_put_idx(struct array_list *arr, size_t idx, void *data)
int array_list_put_idx(struct array_list *arr, size_t idx, void *data)
{
Expand Down Expand Up @@ -165,6 +190,8 @@ int array_list_del_idx(struct array_list *arr, size_t idx, size_t count)
return -1;
for (i = idx; i < stop; ++i)
{
// Because put_idx can skip entries, we need to check if
// there's actually anything in each slot we're erasing.
if (arr->array[i])
arr->free_fn(arr->array[i]);
}
Expand Down
27 changes: 27 additions & 0 deletions arraylist.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,27 @@ struct array_list
};
typedef struct array_list array_list;

/**
* Allocate an array_list of the default size (32).
* @deprecated Use array_list_new2() instead.
*/
extern struct array_list *array_list_new(array_list_free_fn *free_fn);

/**
* Allocate an array_list of the desired size.
*
* If possible, the size should be chosen to closely match
* the actual number of elements expected to be used.
* If the exact size is unknown, there are tradeoffs to be made:
* - too small - the array_list code will need to call realloc() more
* often (which might incur an additional memory copy).
* - too large - will waste memory, but that can be mitigated
* by calling array_list_shrink() once the final size is known.
*
* @see array_list_shrink
*/
extern struct array_list *array_list_new2(array_list_free_fn *free_fn, int initial_size);

extern void array_list_free(struct array_list *al);

extern void *array_list_get_idx(struct array_list *al, size_t i);
Expand All @@ -56,6 +75,14 @@ extern void *array_list_bsearch(const void **key, struct array_list *arr,

extern int array_list_del_idx(struct array_list *arr, size_t idx, size_t count);


/**
* Shrink the array list to just enough to fit the number of elements in it,
* plus empty_slots.
*/
extern int array_list_shrink(struct array_list *arr, size_t empty_slots);


#ifdef __cplusplus
}
#endif
Expand Down
13 changes: 12 additions & 1 deletion json_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1431,11 +1431,15 @@ static void json_object_array_delete(struct json_object *jso)
}

struct json_object *json_object_new_array(void)
{
return json_object_new_array_ext(ARRAY_LIST_DEFAULT_SIZE);
}
struct json_object *json_object_new_array_ext(int initial_size)
{
struct json_object_array *jso = JSON_OBJECT_NEW(array);
if (!jso)
return NULL;
jso->c_array = array_list_new(&json_object_array_entry_free);
jso->c_array = array_list_new2(&json_object_array_entry_free, initial_size);
if (jso->c_array == NULL)
{
free(jso);
Expand Down Expand Up @@ -1523,6 +1527,13 @@ static int json_array_equal(struct json_object *jso1, struct json_object *jso2)
return 1;
}

int json_object_array_shrink(struct json_object *jso, int empty_slots)
{
if (empty_slots < 0)
json_abort("json_object_array_shrink called with negative empty_slots");
return array_list_shrink(JC_ARRAY(jso)->c_array, empty_slots);
}

struct json_object *json_object_new_null(void)
{
return NULL;
Expand Down
15 changes: 15 additions & 0 deletions json_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,16 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key
/* Array type methods */

/** Create a new empty json_object of type json_type_array
* If you know the array size you'll need ahead of time, use
* json_object_new_array_ext() instead.
* @see json_object_new_array_ext()
* @see json_object_array_shrink()
* @returns a json_object of type json_type_array
*/
JSON_EXPORT struct json_object *json_object_new_array(void);

JSON_EXPORT struct json_object *json_object_new_array_ext(int initial_size);

/** Get the arraylist of a json_object of type json_type_array
* @param obj the json_object instance
* @returns an arraylist
Expand Down Expand Up @@ -595,6 +601,15 @@ JSON_EXPORT struct json_object *json_object_array_get_idx(const struct json_obje
*/
JSON_EXPORT int json_object_array_del_idx(struct json_object *obj, size_t idx, size_t count);

/**
* Shrink the internal memory allocation of the array to just
* enough to fit the number of elements in it, plus empty_slots.
*
* @param jso the json_object instance, must be json_type_array
* @param empty_slots the number of empty slots to leave allocated
*/
JSON_EXPORT int json_object_array_shrink(struct json_object *jso, int empty_slots);

/* json_bool type methods */

/** Create a new empty json_object of type json_type_boolean
Expand Down
6 changes: 6 additions & 0 deletions json_tokener.c
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,9 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
case json_tokener_state_array:
if (c == ']')
{
// Minimize memory usage; assume parsed objs are unlikely to be changed
json_object_array_shrink(current, 0);

if (state == json_tokener_state_array_after_sep &&
(tok->flags & JSON_TOKENER_STRICT))
{
Expand Down Expand Up @@ -997,6 +1000,9 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
case json_tokener_state_array_sep:
if (c == ']')
{
// Minimize memory usage; assume parsed objs are unlikely to be changed
json_object_array_shrink(current, 0);

saved_state = json_tokener_state_finish;
state = json_tokener_state_eatws;
}
Expand Down

0 comments on commit e26a119

Please sign in to comment.