Skip to content

Commit 02f79ef

Browse files
authored
Merge pull request #615 from casperisfine/many-opts
Various `JSON.generate` optimizations
2 parents 0e451ec + 4329e30 commit 02f79ef

File tree

3 files changed

+86
-110
lines changed

3 files changed

+86
-110
lines changed

ext/json/ext/fbuffer/fbuffer.h

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,15 @@ typedef struct FBufferStruct {
5555

5656
static FBuffer *fbuffer_alloc(unsigned long initial_length);
5757
static void fbuffer_free(FBuffer *fb);
58+
#ifndef JSON_GENERATOR
5859
static void fbuffer_clear(FBuffer *fb);
60+
#endif
5961
static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len);
6062
#ifdef JSON_GENERATOR
6163
static void fbuffer_append_long(FBuffer *fb, long number);
6264
#endif
6365
static void fbuffer_append_char(FBuffer *fb, char newchr);
6466
#ifdef JSON_GENERATOR
65-
static FBuffer *fbuffer_dup(FBuffer *fb);
6667
static VALUE fbuffer_to_s(FBuffer *fb);
6768
#endif
6869

@@ -86,10 +87,12 @@ static void fbuffer_free(FBuffer *fb)
8687
ruby_xfree(fb);
8788
}
8889

90+
#ifndef JSON_GENERATOR
8991
static void fbuffer_clear(FBuffer *fb)
9092
{
9193
fb->len = 0;
9294
}
95+
#endif
9396

9497
static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested)
9598
{
@@ -168,16 +171,6 @@ static void fbuffer_append_long(FBuffer *fb, long number)
168171
fbuffer_append(fb, buf, len);
169172
}
170173

171-
static FBuffer *fbuffer_dup(FBuffer *fb)
172-
{
173-
unsigned long len = fb->len;
174-
FBuffer *result;
175-
176-
result = fbuffer_alloc(len);
177-
fbuffer_append(result, FBUFFER_PAIR(fb));
178-
return result;
179-
}
180-
181174
static VALUE fbuffer_to_s(FBuffer *fb)
182175
{
183176
VALUE result = rb_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));

ext/json/ext/generator/generator.c

Lines changed: 82 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#include "../fbuffer/fbuffer.h"
22
#include "generator.h"
33

4+
#ifndef RB_UNLIKELY
5+
#define RB_UNLIKELY(cond) (cond)
6+
#endif
7+
48
static VALUE mJSON, mExt, mGenerator, cState, mGeneratorMethods, mObject,
59
mHash, mArray,
610
#ifdef RUBY_INTEGER_UNIFICATION
@@ -417,9 +421,6 @@ static void State_free(void *ptr)
417421
if (state->space_before) ruby_xfree(state->space_before);
418422
if (state->object_nl) ruby_xfree(state->object_nl);
419423
if (state->array_nl) ruby_xfree(state->array_nl);
420-
if (state->array_delim) fbuffer_free(state->array_delim);
421-
if (state->object_delim) fbuffer_free(state->object_delim);
422-
if (state->object_delim2) fbuffer_free(state->object_delim2);
423424
ruby_xfree(state);
424425
}
425426

@@ -432,9 +433,6 @@ static size_t State_memsize(const void *ptr)
432433
if (state->space_before) size += state->space_before_len + 1;
433434
if (state->object_nl) size += state->object_nl_len + 1;
434435
if (state->array_nl) size += state->array_nl_len + 1;
435-
if (state->array_delim) size += FBUFFER_CAPA(state->array_delim);
436-
if (state->object_delim) size += FBUFFER_CAPA(state->object_delim);
437-
if (state->object_delim2) size += FBUFFER_CAPA(state->object_delim2);
438436
return size;
439437
}
440438

@@ -644,24 +642,16 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
644642
JSON_Generator_State *state = arg->state;
645643
VALUE Vstate = arg->Vstate;
646644

647-
char *object_nl = state->object_nl;
648-
long object_nl_len = state->object_nl_len;
649-
char *indent = state->indent;
650-
long indent_len = state->indent_len;
651-
char *delim = FBUFFER_PTR(state->object_delim);
652-
long delim_len = FBUFFER_LEN(state->object_delim);
653-
char *delim2 = FBUFFER_PTR(state->object_delim2);
654-
long delim2_len = FBUFFER_LEN(state->object_delim2);
655645
long depth = state->depth;
656646
int j;
657647

658-
if (arg->iter > 0) fbuffer_append(buffer, delim, delim_len);
659-
if (object_nl) {
660-
fbuffer_append(buffer, object_nl, object_nl_len);
648+
if (arg->iter > 0) fbuffer_append_char(buffer, ',');
649+
if (RB_UNLIKELY(state->object_nl)) {
650+
fbuffer_append(buffer, state->object_nl, state->object_nl_len);
661651
}
662-
if (indent) {
652+
if (RB_UNLIKELY(state->indent)) {
663653
for (j = 0; j < depth; j++) {
664-
fbuffer_append(buffer, indent, indent_len);
654+
fbuffer_append(buffer, state->indent, state->indent_len);
665655
}
666656
}
667657

@@ -678,8 +668,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
678668
break;
679669
}
680670

681-
generate_json(buffer, Vstate, state, key_to_s);
682-
fbuffer_append(buffer, delim2, delim2_len);
671+
generate_json_string(buffer, Vstate, state, key_to_s);
672+
if (RB_UNLIKELY(state->space_before)) fbuffer_append(buffer, state->space_before, state->space_before_len);
673+
fbuffer_append_char(buffer, ':');
674+
if (RB_UNLIKELY(state->space)) fbuffer_append(buffer, state->space, state->space_len);
683675
generate_json(buffer, Vstate, state, val);
684676

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

689681
static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
690682
{
691-
char *object_nl = state->object_nl;
692-
long object_nl_len = state->object_nl_len;
693-
char *indent = state->indent;
694-
long indent_len = state->indent_len;
695683
long max_nesting = state->max_nesting;
696684
long depth = ++state->depth;
697685
int j;
@@ -709,11 +697,11 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
709697
rb_hash_foreach(obj, json_object_i, (VALUE)&arg);
710698

711699
depth = --state->depth;
712-
if (object_nl) {
713-
fbuffer_append(buffer, object_nl, object_nl_len);
714-
if (indent) {
700+
if (RB_UNLIKELY(state->object_nl)) {
701+
fbuffer_append(buffer, state->object_nl, state->object_nl_len);
702+
if (RB_UNLIKELY(state->indent)) {
715703
for (j = 0; j < depth; j++) {
716-
fbuffer_append(buffer, indent, indent_len);
704+
fbuffer_append(buffer, state->indent, state->indent_len);
717705
}
718706
}
719707
}
@@ -722,52 +710,51 @@ static void generate_json_object(FBuffer *buffer, VALUE Vstate, JSON_Generator_S
722710

723711
static void generate_json_array(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
724712
{
725-
char *array_nl = state->array_nl;
726-
long array_nl_len = state->array_nl_len;
727-
char *indent = state->indent;
728-
long indent_len = state->indent_len;
729713
long max_nesting = state->max_nesting;
730-
char *delim = FBUFFER_PTR(state->array_delim);
731-
long delim_len = FBUFFER_LEN(state->array_delim);
732714
long depth = ++state->depth;
733715
int i, j;
734716
if (max_nesting != 0 && depth > max_nesting) {
735717
rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth);
736718
}
737719
fbuffer_append_char(buffer, '[');
738-
if (array_nl) fbuffer_append(buffer, array_nl, array_nl_len);
720+
if (RB_UNLIKELY(state->array_nl)) fbuffer_append(buffer, state->array_nl, state->array_nl_len);
739721
for(i = 0; i < RARRAY_LEN(obj); i++) {
740-
if (i > 0) fbuffer_append(buffer, delim, delim_len);
741-
if (indent) {
722+
if (i > 0) {
723+
fbuffer_append_char(buffer, ',');
724+
if (RB_UNLIKELY(state->array_nl)) fbuffer_append(buffer, state->array_nl, state->array_nl_len);
725+
}
726+
if (RB_UNLIKELY(state->indent)) {
742727
for (j = 0; j < depth; j++) {
743-
fbuffer_append(buffer, indent, indent_len);
728+
fbuffer_append(buffer, state->indent, state->indent_len);
744729
}
745730
}
746-
generate_json(buffer, Vstate, state, rb_ary_entry(obj, i));
731+
generate_json(buffer, Vstate, state, RARRAY_AREF(obj, i));
747732
}
748733
state->depth = --depth;
749-
if (array_nl) {
750-
fbuffer_append(buffer, array_nl, array_nl_len);
751-
if (indent) {
734+
if (RB_UNLIKELY(state->array_nl)) {
735+
fbuffer_append(buffer, state->array_nl, state->array_nl_len);
736+
if (RB_UNLIKELY(state->indent)) {
752737
for (j = 0; j < depth; j++) {
753-
fbuffer_append(buffer, indent, indent_len);
738+
fbuffer_append(buffer, state->indent, state->indent_len);
754739
}
755740
}
756741
}
757742
fbuffer_append_char(buffer, ']');
758743
}
759744

760-
static int enc_utf8_compatible_p(rb_encoding *enc)
745+
static int usascii_encindex, utf8_encindex;
746+
747+
static int enc_utf8_compatible_p(int enc_idx)
761748
{
762-
if (enc == rb_usascii_encoding()) return 1;
763-
if (enc == rb_utf8_encoding()) return 1;
749+
if (enc_idx == usascii_encindex) return 1;
750+
if (enc_idx == utf8_encindex) return 1;
764751
return 0;
765752
}
766753

767754
static void generate_json_string(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
768755
{
769756
fbuffer_append_char(buffer, '"');
770-
if (!enc_utf8_compatible_p(rb_enc_get(obj))) {
757+
if (!enc_utf8_compatible_p(RB_ENCODING_GET(obj))) {
771758
obj = rb_str_export_to_enc(obj, rb_utf8_encoding());
772759
}
773760
convert_UTF8_to_JSON(buffer, obj, state->ascii_only, state->script_safe);
@@ -827,35 +814,56 @@ static void generate_json_float(FBuffer *buffer, VALUE Vstate, JSON_Generator_St
827814
static void generate_json(FBuffer *buffer, VALUE Vstate, JSON_Generator_State *state, VALUE obj)
828815
{
829816
VALUE tmp;
830-
VALUE klass = CLASS_OF(obj);
831-
if (klass == rb_cHash) {
832-
generate_json_object(buffer, Vstate, state, obj);
833-
} else if (klass == rb_cArray) {
834-
generate_json_array(buffer, Vstate, state, obj);
835-
} else if (klass == rb_cString) {
836-
generate_json_string(buffer, Vstate, state, obj);
837-
} else if (obj == Qnil) {
817+
if (obj == Qnil) {
838818
generate_json_null(buffer, Vstate, state, obj);
839819
} else if (obj == Qfalse) {
840820
generate_json_false(buffer, Vstate, state, obj);
841821
} else if (obj == Qtrue) {
842822
generate_json_true(buffer, Vstate, state, obj);
843-
} else if (FIXNUM_P(obj)) {
844-
generate_json_fixnum(buffer, Vstate, state, obj);
845-
} else if (RB_TYPE_P(obj, T_BIGNUM)) {
846-
generate_json_bignum(buffer, Vstate, state, obj);
847-
} else if (klass == rb_cFloat) {
848-
generate_json_float(buffer, Vstate, state, obj);
849-
} else if (state->strict) {
850-
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(CLASS_OF(obj)));
851-
} else if (rb_respond_to(obj, i_to_json)) {
852-
tmp = rb_funcall(obj, i_to_json, 1, Vstate);
853-
Check_Type(tmp, T_STRING);
854-
fbuffer_append_str(buffer, tmp);
823+
} else if (RB_SPECIAL_CONST_P(obj)) {
824+
if (RB_FIXNUM_P(obj)) {
825+
generate_json_fixnum(buffer, Vstate, state, obj);
826+
} else if (RB_FLONUM_P(obj)) {
827+
generate_json_float(buffer, Vstate, state, obj);
828+
} else {
829+
goto general;
830+
}
855831
} else {
856-
tmp = rb_funcall(obj, i_to_s, 0);
857-
Check_Type(tmp, T_STRING);
858-
generate_json_string(buffer, Vstate, state, tmp);
832+
VALUE klass = RBASIC_CLASS(obj);
833+
switch (RB_BUILTIN_TYPE(obj)) {
834+
case T_BIGNUM:
835+
generate_json_bignum(buffer, Vstate, state, obj);
836+
break;
837+
case T_HASH:
838+
if (klass != rb_cHash) goto general;
839+
generate_json_object(buffer, Vstate, state, obj);
840+
break;
841+
case T_ARRAY:
842+
if (klass != rb_cArray) goto general;
843+
generate_json_array(buffer, Vstate, state, obj);
844+
break;
845+
case T_STRING:
846+
if (klass != rb_cString) goto general;
847+
generate_json_string(buffer, Vstate, state, obj);
848+
break;
849+
case T_FLOAT:
850+
if (klass != rb_cFloat) goto general;
851+
generate_json_float(buffer, Vstate, state, obj);
852+
break;
853+
default:
854+
general:
855+
if (state->strict) {
856+
rb_raise(eGeneratorError, "%"PRIsVALUE" not allowed in JSON", RB_OBJ_STRING(CLASS_OF(obj)));
857+
} else if (rb_respond_to(obj, i_to_json)) {
858+
tmp = rb_funcall(obj, i_to_json, 1, Vstate);
859+
Check_Type(tmp, T_STRING);
860+
fbuffer_append_str(buffer, tmp);
861+
} else {
862+
tmp = rb_funcall(obj, i_to_s, 0);
863+
Check_Type(tmp, T_STRING);
864+
generate_json_string(buffer, Vstate, state, tmp);
865+
}
866+
}
859867
}
860868
}
861869

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

868-
if (state->object_delim) {
869-
fbuffer_clear(state->object_delim);
870-
} else {
871-
state->object_delim = fbuffer_alloc(16);
872-
}
873-
fbuffer_append_char(state->object_delim, ',');
874-
if (state->object_delim2) {
875-
fbuffer_clear(state->object_delim2);
876-
} else {
877-
state->object_delim2 = fbuffer_alloc(16);
878-
}
879-
if (state->space_before) fbuffer_append(state->object_delim2, state->space_before, state->space_before_len);
880-
fbuffer_append_char(state->object_delim2, ':');
881-
if (state->space) fbuffer_append(state->object_delim2, state->space, state->space_len);
882-
883-
if (state->array_delim) {
884-
fbuffer_clear(state->array_delim);
885-
} else {
886-
state->array_delim = fbuffer_alloc(16);
887-
}
888-
fbuffer_append_char(state->array_delim, ',');
889-
if (state->array_nl) fbuffer_append(state->array_delim, state->array_nl, state->array_nl_len);
890876
return buffer;
891877
}
892878

@@ -999,9 +985,6 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
999985
objState->space_before = fstrndup(origState->space_before, origState->space_before_len);
1000986
objState->object_nl = fstrndup(origState->object_nl, origState->object_nl_len);
1001987
objState->array_nl = fstrndup(origState->array_nl, origState->array_nl_len);
1002-
if (origState->array_delim) objState->array_delim = fbuffer_dup(origState->array_delim);
1003-
if (origState->object_delim) objState->object_delim = fbuffer_dup(origState->object_delim);
1004-
if (origState->object_delim2) objState->object_delim2 = fbuffer_dup(origState->object_delim2);
1005988
return obj;
1006989
}
1007990

@@ -1498,4 +1481,7 @@ void Init_generator(void)
14981481
i_match = rb_intern("match");
14991482
i_keys = rb_intern("keys");
15001483
i_dup = rb_intern("dup");
1484+
1485+
usascii_encindex = rb_usascii_encindex();
1486+
utf8_encindex = rb_utf8_encindex();
15011487
}

ext/json/ext/generator/generator.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ typedef struct JSON_Generator_StateStruct {
5555
long object_nl_len;
5656
char *array_nl;
5757
long array_nl_len;
58-
FBuffer *array_delim;
59-
FBuffer *object_delim;
60-
FBuffer *object_delim2;
6158
long max_nesting;
6259
char allow_nan;
6360
char ascii_only;

0 commit comments

Comments
 (0)