Skip to content

Commit

Permalink
Merge pull request #615 from casperisfine/many-opts
Browse files Browse the repository at this point in the history
Various `JSON.generate` optimizations
  • Loading branch information
byroot authored Oct 17, 2024
2 parents 0e451ec + 4329e30 commit 02f79ef
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 110 deletions.
15 changes: 4 additions & 11 deletions ext/json/ext/fbuffer/fbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ typedef struct FBufferStruct {

static FBuffer *fbuffer_alloc(unsigned long initial_length);
static void fbuffer_free(FBuffer *fb);
#ifndef JSON_GENERATOR
static void fbuffer_clear(FBuffer *fb);
#endif
static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len);
#ifdef JSON_GENERATOR
static void fbuffer_append_long(FBuffer *fb, long number);
#endif
static void fbuffer_append_char(FBuffer *fb, char newchr);
#ifdef JSON_GENERATOR
static FBuffer *fbuffer_dup(FBuffer *fb);
static VALUE fbuffer_to_s(FBuffer *fb);
#endif

Expand All @@ -86,10 +87,12 @@ static void fbuffer_free(FBuffer *fb)
ruby_xfree(fb);
}

#ifndef JSON_GENERATOR
static void fbuffer_clear(FBuffer *fb)
{
fb->len = 0;
}
#endif

static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
{
Expand Down Expand Up @@ -168,16 +171,6 @@ static void fbuffer_append_long(FBuffer *fb, long number)
fbuffer_append(fb, buf, len);
}

static FBuffer *fbuffer_dup(FBuffer *fb)
{
unsigned long len = fb->len;
FBuffer *result;

result = fbuffer_alloc(len);
fbuffer_append(result, FBUFFER_PAIR(fb));
return result;
}

static VALUE fbuffer_to_s(FBuffer *fb)
{
VALUE result = rb_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
Expand Down
178 changes: 82 additions & 96 deletions ext/json/ext/generator/generator.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include "../fbuffer/fbuffer.h"
#include "generator.h"

#ifndef RB_UNLIKELY
#define RB_UNLIKELY(cond) (cond)
#endif

static VALUE mJSON, mExt, mGenerator, cState, mGeneratorMethods, mObject,
mHash, mArray,
#ifdef RUBY_INTEGER_UNIFICATION
Expand Down Expand Up @@ -417,9 +421,6 @@ static void State_free(void *ptr)
if (state->space_before) ruby_xfree(state->space_before);
if (state->object_nl) ruby_xfree(state->object_nl);
if (state->array_nl) ruby_xfree(state->array_nl);
if (state->array_delim) fbuffer_free(state->array_delim);
if (state->object_delim) fbuffer_free(state->object_delim);
if (state->object_delim2) fbuffer_free(state->object_delim2);
ruby_xfree(state);
}

Expand All @@ -432,9 +433,6 @@ static size_t State_memsize(const void *ptr)
if (state->space_before) size += state->space_before_len + 1;
if (state->object_nl) size += state->object_nl_len + 1;
if (state->array_nl) size += state->array_nl_len + 1;
if (state->array_delim) size += FBUFFER_CAPA(state->array_delim);
if (state->object_delim) size += FBUFFER_CAPA(state->object_delim);
if (state->object_delim2) size += FBUFFER_CAPA(state->object_delim2);
return size;
}

Expand Down Expand Up @@ -644,24 +642,16 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
JSON_Generator_State *state = arg->state;
VALUE Vstate = arg->Vstate;

char *object_nl = state->object_nl;
long object_nl_len = state->object_nl_len;
char *indent = state->indent;
long indent_len = state->indent_len;
char *delim = FBUFFER_PTR(state->object_delim);
long delim_len = FBUFFER_LEN(state->object_delim);
char *delim2 = FBUFFER_PTR(state->object_delim2);
long delim2_len = FBUFFER_LEN(state->object_delim2);
long depth = state->depth;
int j;

if (arg->iter > 0) fbuffer_append(buffer, delim, delim_len);
if (object_nl) {
fbuffer_append(buffer, object_nl, object_nl_len);
if (arg->iter > 0) fbuffer_append_char(buffer, ',');
if (RB_UNLIKELY(state->object_nl)) {
fbuffer_append(buffer, state->object_nl, state->object_nl_len);
}
if (indent) {
if (RB_UNLIKELY(state->indent)) {
for (j = 0; j < depth; j++) {
fbuffer_append(buffer, indent, indent_len);
fbuffer_append(buffer, state->indent, state->indent_len);
}
}

Expand All @@ -678,8 +668,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
break;
}

generate_json(buffer, Vstate, state, key_to_s);
fbuffer_append(buffer, delim2, delim2_len);
generate_json_string(buffer, Vstate, state, key_to_s);
if (RB_UNLIKELY(state->space_before)) fbuffer_append(buffer, state->space_before, state->space_before_len);
fbuffer_append_char(buffer, ':');
if (RB_UNLIKELY(state->space)) fbuffer_append(buffer, state->space, state->space_len);
generate_json(buffer, Vstate, state, val);

arg->iter++;
Expand All @@ -688,10 +680,6 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)

static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
char *object_nl = state->object_nl;
long object_nl_len = state->object_nl_len;
char *indent = state->indent;
long indent_len = state->indent_len;
long max_nesting = state->max_nesting;
long depth = ++state->depth;
int j;
Expand All @@ -709,11 +697,11 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
rb_hash_foreach(obj, json_object_i, (VALUE)&arg);

depth = --state->depth;
if (object_nl) {
fbuffer_append(buffer, object_nl, object_nl_len);
if (indent) {
if (RB_UNLIKELY(state->object_nl)) {
fbuffer_append(buffer, state->object_nl, state->object_nl_len);
if (RB_UNLIKELY(state->indent)) {
for (j = 0; j < depth; j++) {
fbuffer_append(buffer, indent, indent_len);
fbuffer_append(buffer, state->indent, state->indent_len);
}
}
}
Expand All @@ -722,52 +710,51 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S

static void generate_json_array(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
char *array_nl = state->array_nl;
long array_nl_len = state->array_nl_len;
char *indent = state->indent;
long indent_len = state->indent_len;
long max_nesting = state->max_nesting;
char *delim = FBUFFER_PTR(state->array_delim);
long delim_len = FBUFFER_LEN(state->array_delim);
long depth = ++state->depth;
int i, j;
if (max_nesting != 0 && depth > max_nesting) {
rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
}
fbuffer_append_char(buffer, '[');
if (array_nl) fbuffer_append(buffer, array_nl, array_nl_len);
if (RB_UNLIKELY(state->array_nl)) fbuffer_append(buffer, state->array_nl, state->array_nl_len);
for(i = 0; i < RARRAY_LEN(obj); i++) {
if (i > 0) fbuffer_append(buffer, delim, delim_len);
if (indent) {
if (i > 0) {
fbuffer_append_char(buffer, ',');
if (RB_UNLIKELY(state->array_nl)) fbuffer_append(buffer, state->array_nl, state->array_nl_len);
}
if (RB_UNLIKELY(state->indent)) {
for (j = 0; j < depth; j++) {
fbuffer_append(buffer, indent, indent_len);
fbuffer_append(buffer, state->indent, state->indent_len);
}
}
generate_json(buffer, Vstate, state, rb_ary_entry(obj, i));
generate_json(buffer, Vstate, state, RARRAY_AREF(obj, i));
}
state->depth = --depth;
if (array_nl) {
fbuffer_append(buffer, array_nl, array_nl_len);
if (indent) {
if (RB_UNLIKELY(state->array_nl)) {
fbuffer_append(buffer, state->array_nl, state->array_nl_len);
if (RB_UNLIKELY(state->indent)) {
for (j = 0; j < depth; j++) {
fbuffer_append(buffer, indent, indent_len);
fbuffer_append(buffer, state->indent, state->indent_len);
}
}
}
fbuffer_append_char(buffer, ']');
}

static int enc_utf8_compatible_p(rb_encoding *enc)
static int usascii_encindex, utf8_encindex;

static int enc_utf8_compatible_p(int enc_idx)
{
if (enc == rb_usascii_encoding()) return 1;
if (enc == rb_utf8_encoding()) return 1;
if (enc_idx == usascii_encindex) return 1;
if (enc_idx == utf8_encindex) return 1;
return 0;
}

static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
fbuffer_append_char(buffer, '"');
if (!enc_utf8_compatible_p(rb_enc_get(obj))) {
if (!enc_utf8_compatible_p(RB_ENCODING_GET(obj))) {
obj = rb_str_export_to_enc(obj, rb_utf8_encoding());
}
convert_UTF8_to_JSON(buffer, obj, state->ascii_only, state->script_safe);
Expand Down Expand Up @@ -827,35 +814,56 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
{
VALUE tmp;
VALUE klass = CLASS_OF(obj);
if (klass == rb_cHash) {
generate_json_object(buffer, Vstate, state, obj);
} else if (klass == rb_cArray) {
generate_json_array(buffer, Vstate, state, obj);
} else if (klass == rb_cString) {
generate_json_string(buffer, Vstate, state, obj);
} else if (obj == Qnil) {
if (obj == Qnil) {
generate_json_null(buffer, Vstate, state, obj);
} else if (obj == Qfalse) {
generate_json_false(buffer, Vstate, state, obj);
} else if (obj == Qtrue) {
generate_json_true(buffer, Vstate, state, obj);
} else if (FIXNUM_P(obj)) {
generate_json_fixnum(buffer, Vstate, state, obj);
} else if (RB_TYPE_P(obj, T_BIGNUM)) {
generate_json_bignum(buffer, Vstate, state, obj);
} else if (klass == rb_cFloat) {
generate_json_float(buffer, Vstate, state, obj);
} else if (state->strict) {
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(CLASS_OF(obj)));
} else if (rb_respond_to(obj, i_to_json)) {
tmp = rb_funcall(obj, i_to_json, 1, Vstate);
Check_Type(tmp, T_STRING);
fbuffer_append_str(buffer, tmp);
} else if (RB_SPECIAL_CONST_P(obj)) {
if (RB_FIXNUM_P(obj)) {
generate_json_fixnum(buffer, Vstate, state, obj);
} else if (RB_FLONUM_P(obj)) {
generate_json_float(buffer, Vstate, state, obj);
} else {
goto general;
}
} else {
tmp = rb_funcall(obj, i_to_s, 0);
Check_Type(tmp, T_STRING);
generate_json_string(buffer, Vstate, state, tmp);
VALUE klass = RBASIC_CLASS(obj);
switch (RB_BUILTIN_TYPE(obj)) {
case T_BIGNUM:
generate_json_bignum(buffer, Vstate, state, obj);
break;
case T_HASH:
if (klass != rb_cHash) goto general;
generate_json_object(buffer, Vstate, state, obj);
break;
case T_ARRAY:
if (klass != rb_cArray) goto general;
generate_json_array(buffer, Vstate, state, obj);
break;
case T_STRING:
if (klass != rb_cString) goto general;
generate_json_string(buffer, Vstate, state, obj);
break;
case T_FLOAT:
if (klass != rb_cFloat) goto general;
generate_json_float(buffer, Vstate, state, obj);
break;
default:
general:
if (state->strict) {
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(CLASS_OF(obj)));
} else if (rb_respond_to(obj, i_to_json)) {
tmp = rb_funcall(obj, i_to_json, 1, Vstate);
Check_Type(tmp, T_STRING);
fbuffer_append_str(buffer, tmp);
} else {
tmp = rb_funcall(obj, i_to_s, 0);
Check_Type(tmp, T_STRING);
generate_json_string(buffer, Vstate, state, tmp);
}
}
}
}

Expand All @@ -865,28 +873,6 @@ static FBuffer *cState_prepare_buffer(VALUE self)
GET_STATE(self);
buffer = fbuffer_alloc(state->buffer_initial_length);

if (state->object_delim) {
fbuffer_clear(state->object_delim);
} else {
state->object_delim = fbuffer_alloc(16);
}
fbuffer_append_char(state->object_delim, ',');
if (state->object_delim2) {
fbuffer_clear(state->object_delim2);
} else {
state->object_delim2 = fbuffer_alloc(16);
}
if (state->space_before) fbuffer_append(state->object_delim2, state->space_before, state->space_before_len);
fbuffer_append_char(state->object_delim2, ':');
if (state->space) fbuffer_append(state->object_delim2, state->space, state->space_len);

if (state->array_delim) {
fbuffer_clear(state->array_delim);
} else {
state->array_delim = fbuffer_alloc(16);
}
fbuffer_append_char(state->array_delim, ',');
if (state->array_nl) fbuffer_append(state->array_delim, state->array_nl, state->array_nl_len);
return buffer;
}

Expand Down Expand Up @@ -999,9 +985,6 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
objState->space_before = fstrndup(origState->space_before, origState->space_before_len);
objState->object_nl = fstrndup(origState->object_nl, origState->object_nl_len);
objState->array_nl = fstrndup(origState->array_nl, origState->array_nl_len);
if (origState->array_delim) objState->array_delim = fbuffer_dup(origState->array_delim);
if (origState->object_delim) objState->object_delim = fbuffer_dup(origState->object_delim);
if (origState->object_delim2) objState->object_delim2 = fbuffer_dup(origState->object_delim2);
return obj;
}

Expand Down Expand Up @@ -1498,4 +1481,7 @@ void Init_generator(void)
i_match = rb_intern("match");
i_keys = rb_intern("keys");
i_dup = rb_intern("dup");

usascii_encindex = rb_usascii_encindex();
utf8_encindex = rb_utf8_encindex();
}
3 changes: 0 additions & 3 deletions ext/json/ext/generator/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ typedef struct JSON_Generator_StateStruct {
long object_nl_len;
char *array_nl;
long array_nl_len;
FBuffer *array_delim;
FBuffer *object_delim;
FBuffer *object_delim2;
long max_nesting;
char allow_nan;
char ascii_only;
Expand Down

0 comments on commit 02f79ef

Please sign in to comment.