Also add the json_object_new_array_ext, array_list_new2, and array_list_shrink functions.tags/json-c-0.15-20200726
@@ -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 | |||
-------------- | |||
@@ -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. | |||
*** | |||
@@ -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 *)))) | |||
@@ -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) | |||
{ | |||
@@ -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]); | |||
} | |||
@@ -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); | |||
@@ -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 | |||
@@ -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); | |||
@@ -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; | |||
@@ -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 | |||
@@ -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 | |||
@@ -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)) | |||
{ | |||
@@ -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; | |||
} | |||