This is an automated email from the ASF dual-hosted git repository. nickva pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git
commit 172c8bdc252d6eecb76b92cfdd3235ee27c9cf27 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Sun Jun 14 01:06:04 2026 -0400 Reduce encoder memory allocations Flush the buffer into iolist segments with a chunk size that doubles (up to 64KB). Large documents should now have O(log n) allocation. We can use tprof to check allocation with OTP 27+. Example of memory profiling before and after with `canada.json` from bench data: ``` {ok, B} = file:read_file("data/canada.json"). T = jiffy:decode(B, [return_maps]). tprof:profile(jiffy, encode, [T], #{type => call_memory}). ``` Before ``` ****** Process <0.114.0> -- 100.00% of total *** FUNCTION CALLS WORDS PER CALL [ %] lists:reverse/1 1 4 4.00 [ 0.03] lists:reverse/2 1 2062 2062.00 [14.21] jiffy:nif_encode_init/2 1 12441 12441.00 [85.76] 14507 [100.0] ``` After ``` ****** Process <0.101.0> -- 100.00% of total *** FUNCTION CALLS WORDS PER CALL [ %] lists:reverse/1 1 4 4.00 [ 0.29] lists:reverse/2 1 68 68.00 [ 4.88] jiffy:nif_encode_init/2 1 1321 1321.00 [94.83] ``` This shows a good speed improvment on some benchmarks especially ones with larger objects. Saw about ~6% speedup on on object-heavy benchmarks (citm, pokedex, github):. Larger ones like "canada" showed about a ~1.25x speedup. --- c_src/encoder.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/c_src/encoder.c b/c_src/encoder.c index 413ce79..fa4bf98 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -11,6 +11,12 @@ #define BIN_INC_SIZE 2048 +// When we flush output buffer to an iolist double the next chunk size so a +// large doc is emitted in O(log n) segments. But we also limit it at 64KB +// size. This should reduce total number of memory allocations (those can +// become a bottleneck). +#define MAX_CHUNK_SIZE (64 * 1024) + #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) #define SMALL_TERMSTACK_SIZE 16 @@ -51,6 +57,7 @@ typedef struct { ErlNifBinary buffer; int have_buffer; + size_t chunk_size; unsigned char* p; size_t i; @@ -169,8 +176,9 @@ enc_new(ErlNifEnv* env) e->atoms = st; e->bytes_per_red = DEFAULT_BYTES_PER_REDUCTION; e->iolist = enif_make_list(env, 0); + e->chunk_size = BIN_INC_SIZE; - if(!enif_alloc_binary(BIN_INC_SIZE, &e->buffer)) { + if(!enif_alloc_binary(e->chunk_size, &e->buffer)) { enif_release_resource(e); return NULL; } @@ -246,6 +254,11 @@ enc_flush(Encoder* e) e->iolist = enif_make_list_cell(e->env, bin, e->iolist); e->iosize += e->i; + // Grow the next chunk geometrically so large outputs flush O(log n) times. + if(e->chunk_size < MAX_CHUNK_SIZE) { + e->chunk_size <<= 1; + } + return 1; } @@ -268,7 +281,7 @@ enc_ensure(Encoder* e, size_t req) } } - for(new_size = BIN_INC_SIZE; new_size < req; new_size <<= 1); + for(new_size = e->chunk_size; new_size < req; new_size <<= 1); if(!enif_alloc_binary(new_size, &e->buffer)) { return 0;
