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: | Deprecated and removed features: | ||||
| -------------------------------- | -------------------------------- | ||||
| ...none yet... | |||||
| * array_list_new() has been deprecated in favor of array_list_new2() | |||||
| Other changes | Other changes | ||||
| -------------- | -------------- | ||||
| @@ -19,6 +19,12 @@ Other changes | |||||
| less memory usage. | less memory usage. | ||||
| Memory used just for json_object structures decreased 27%, so use cases | Memory used just for json_object structures decreased 27%, so use cases | ||||
| with fewer arrays and/or strings would benefit more. | 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" | #include "arraylist.h" | ||||
| struct array_list *array_list_new(array_list_free_fn *free_fn) | 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; | struct array_list *arr; | ||||
| arr = (struct array_list *)malloc(sizeof(struct array_list)); | arr = (struct array_list *)malloc(sizeof(struct array_list)); | ||||
| if (!arr) | if (!arr) | ||||
| return NULL; | return NULL; | ||||
| arr->size = ARRAY_LIST_DEFAULT_SIZE; | |||||
| arr->size = initial_size; | |||||
| arr->length = 0; | arr->length = 0; | ||||
| arr->free_fn = free_fn; | arr->free_fn = free_fn; | ||||
| if (!(arr->array = (void **)malloc(arr->size * sizeof(void *)))) | 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; | 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) | //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) | 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; | return -1; | ||||
| for (i = idx; i < stop; ++i) | 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]) | if (arr->array[i]) | ||||
| arr->free_fn(arr->array[i]); | arr->free_fn(arr->array[i]); | ||||
| } | } | ||||
| @@ -37,8 +37,27 @@ struct array_list | |||||
| }; | }; | ||||
| typedef struct array_list 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); | 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_free(struct array_list *al); | ||||
| extern void *array_list_get_idx(struct array_list *al, size_t i); | 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); | 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 | #ifdef __cplusplus | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -1431,11 +1431,15 @@ static void json_object_array_delete(struct json_object *jso) | |||||
| } | } | ||||
| struct json_object *json_object_new_array(void) | 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); | struct json_object_array *jso = JSON_OBJECT_NEW(array); | ||||
| if (!jso) | if (!jso) | ||||
| return NULL; | 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) | if (jso->c_array == NULL) | ||||
| { | { | ||||
| free(jso); | free(jso); | ||||
| @@ -1523,6 +1527,13 @@ static int json_array_equal(struct json_object *jso1, struct json_object *jso2) | |||||
| return 1; | 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) | struct json_object *json_object_new_null(void) | ||||
| { | { | ||||
| return NULL; | return NULL; | ||||
| @@ -500,10 +500,16 @@ JSON_EXPORT void json_object_object_del(struct json_object *obj, const char *key | |||||
| /* Array type methods */ | /* Array type methods */ | ||||
| /** Create a new empty json_object of type json_type_array | /** 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 | * @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(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 | /** Get the arraylist of a json_object of type json_type_array | ||||
| * @param obj the json_object instance | * @param obj the json_object instance | ||||
| * @returns an arraylist | * @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); | 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 */ | /* json_bool type methods */ | ||||
| /** Create a new empty json_object of type json_type_boolean | /** 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: | case json_tokener_state_array: | ||||
| if (c == ']') | 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 && | if (state == json_tokener_state_array_after_sep && | ||||
| (tok->flags & JSON_TOKENER_STRICT)) | (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: | case json_tokener_state_array_sep: | ||||
| if (c == ']') | 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; | saved_state = json_tokener_state_finish; | ||||
| state = json_tokener_state_eatws; | state = json_tokener_state_eatws; | ||||
| } | } | ||||