Skip to content

Commit

Permalink
Improve performance of Oj.dump with compat/rails mode
Browse files Browse the repository at this point in the history
This patch introduces `oj_hash_has_key()` (same as rb__hash_has_key)
to reduce `rb_funcall()` calling because it has a overhead.

This patch will improve `Oj.dump` performance as following.

−               | before   | after    | result
--               | --       | --       | --
Oj.dump          | 1.949M   | 1.966M   | −
Oj.dump (compat) | 850.154k | 1.198M   | 1.41x
Oj.dump (rails)  | 657.383k | 840.051k | 1.28x

### Environment
- MacBook Air (M1, 2020)
- macOS 12.0 beta 3
- Apple M1
- Ruby 3.0.2

### Before
```
Warming up --------------------------------------
             Oj.dump   198.379k i/100ms
    Oj.dump (compat)    86.466k i/100ms
     Oj.dump (rails)    66.760k i/100ms
Calculating -------------------------------------
             Oj.dump      1.949M (± 0.3%) i/s -      9.919M in   5.088487s
    Oj.dump (compat)    850.154k (± 0.3%) i/s -      4.323M in   5.085367s
     Oj.dump (rails)    657.383k (± 0.4%) i/s -      3.338M in   5.077802s
```

### After
```
Warming up --------------------------------------
             Oj.dump   198.297k i/100ms
    Oj.dump (compat)   120.402k i/100ms
     Oj.dump (rails)    84.204k i/100ms
Calculating -------------------------------------
             Oj.dump      1.966M (± 0.3%) i/s -      9.915M in   5.044305s
    Oj.dump (compat)      1.198M (± 0.2%) i/s -      6.020M in   5.026524s
     Oj.dump (rails)    840.051k (± 0.1%) i/s -      4.210M in   5.011848s
```

### Test code
```ruby
require 'benchmark/ips'
require 'oj'

data = {
  'short_string': 'a' * 50,
  'long_string': 'b' * 255,
  'utf8_string': 'あいうえお' * 10
}

Benchmark.ips do |x|
  x.report('Oj.dump') { Oj.dump(data) }
  x.report('Oj.dump (compat)') { Oj.dump(data, mode: :compat) }
  x.report('Oj.dump (rails)') { Oj.dump(data, mode: :rails) }
end
```
  • Loading branch information
Watson1978 committed Jul 26, 2021
1 parent cfc14d1 commit ad67f5e
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 24 deletions.
18 changes: 9 additions & 9 deletions ext/oj/mimic_json.c
Expand Up @@ -464,19 +464,19 @@ oj_mimic_pretty_generate(int argc, VALUE *argv, VALUE self) {
} else {
h = argv[1];
}
if (Qfalse == rb_funcall(h, oj_has_key_id, 1, oj_indent_sym)) {
if (!oj_hash_has_key(h, oj_indent_sym)) {
rb_hash_aset(h, oj_indent_sym, rb_str_new2(" "));
}
if (Qfalse == rb_funcall(h, oj_has_key_id, 1, oj_space_before_sym)) {
if (!oj_hash_has_key(h, oj_space_before_sym)) {
rb_hash_aset(h, oj_space_before_sym, rb_str_new2(""));
}
if (Qfalse == rb_funcall(h, oj_has_key_id, 1, oj_space_sym)) {
if (!oj_hash_has_key(h, oj_space_sym)) {
rb_hash_aset(h, oj_space_sym, rb_str_new2(" "));
}
if (Qfalse == rb_funcall(h, oj_has_key_id, 1, oj_object_nl_sym)) {
if (!oj_hash_has_key(h, oj_object_nl_sym)) {
rb_hash_aset(h, oj_object_nl_sym, rb_str_new2("\n"));
}
if (Qfalse == rb_funcall(h, oj_has_key_id, 1, oj_array_nl_sym)) {
if (!oj_hash_has_key(h, oj_array_nl_sym)) {
rb_hash_aset(h, oj_array_nl_sym, rb_str_new2("\n"));
}
if (Qundef == state_class) {
Expand Down Expand Up @@ -548,31 +548,31 @@ static VALUE mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) {
pi.options.allow_nan = (Qtrue == v) ? Yes : No;
}

if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_hash_class_sym)) {
if (oj_hash_has_key(ropts, oj_hash_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_hash_class_sym))) {
pi.options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi.options.hash_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_object_class_sym)) {
if (oj_hash_has_key(ropts, oj_object_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_object_class_sym))) {
pi.options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi.options.hash_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_array_class_sym)) {
if (oj_hash_has_key(ropts, oj_array_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_array_class_sym))) {
pi.options.array_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi.options.array_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_decimal_class_sym)) {
if (oj_hash_has_key(ropts, oj_decimal_class_sym)) {
pi.options.compat_bigdec = (oj_bigdecimal_class ==
rb_hash_lookup(ropts, oj_decimal_class_sym));
}
Expand Down
34 changes: 20 additions & 14 deletions ext/oj/oj.c
Expand Up @@ -40,7 +40,6 @@ ID oj_error_id;
ID oj_file_id;
ID oj_fileno_id;
ID oj_ftype_id;
ID oj_has_key_id;
ID oj_hash_end_id;
ID oj_hash_key_id;
ID oj_hash_set_id;
Expand Down Expand Up @@ -582,6 +581,14 @@ static VALUE set_def_opts(VALUE self, VALUE opts) {
return Qnil;
}

bool oj_hash_has_key(VALUE hash, VALUE key)
{
if (Qundef == rb_hash_lookup2(hash, key, Qundef)) {
return false;
}
return true;
}

void oj_parse_options(VALUE ropts, Options copts) {
struct _yesNoOpt ynos[] = {{circular_sym, &copts->circular},
{auto_define_sym, &copts->auto_define},
Expand Down Expand Up @@ -612,7 +619,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
if (T_HASH != rb_type(ropts)) {
return;
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_indent_sym)) {
if (oj_hash_has_key(ropts, oj_indent_sym)) {
v = rb_hash_lookup(ropts, oj_indent_sym);
switch (rb_type(v)) {
case T_NIL:
Expand Down Expand Up @@ -773,7 +780,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
if (Qnil != (v = rb_hash_lookup(ropts, compat_bigdecimal_sym))) {
copts->compat_bigdec = (Qtrue == v);
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_decimal_class_sym)) {
if (oj_hash_has_key(ropts, oj_decimal_class_sym)) {
v = rb_hash_lookup(ropts, oj_decimal_class_sym);
if (rb_cFloat == v) {
copts->compat_bigdec = false;
Expand All @@ -783,7 +790,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
rb_raise(rb_eArgError, ":decimal_class must be BigDecimal or Float.");
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, create_id_sym)) {
if (oj_hash_has_key(ropts, create_id_sym)) {
v = rb_hash_lookup(ropts, create_id_sym);
if (Qnil == v) {
if (oj_json_class != oj_default_options.create_id && NULL != copts->create_id) {
Expand All @@ -805,7 +812,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
}
}
for (o = ynos; 0 != o->attr; o++) {
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, o->sym)) {
if (oj_hash_has_key(ropts, o->sym)) {
v = rb_hash_lookup(ropts, o->sym);
if (Qnil == v) {
*o->attr = NotSet;
Expand All @@ -820,7 +827,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
}
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_space_sym)) {
if (oj_hash_has_key(ropts, oj_space_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_space_sym))) {
copts->dump_opts.after_size = 0;
*copts->dump_opts.after_sep = '\0';
Expand All @@ -835,7 +842,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
copts->dump_opts.after_size = (uint8_t)len;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_space_before_sym)) {
if (oj_hash_has_key(ropts, oj_space_before_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_space_before_sym))) {
copts->dump_opts.before_size = 0;
*copts->dump_opts.before_sep = '\0';
Expand All @@ -850,7 +857,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
copts->dump_opts.before_size = (uint8_t)len;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_object_nl_sym)) {
if (oj_hash_has_key(ropts, oj_object_nl_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_object_nl_sym))) {
copts->dump_opts.hash_size = 0;
*copts->dump_opts.hash_nl = '\0';
Expand All @@ -865,7 +872,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
copts->dump_opts.hash_size = (uint8_t)len;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_array_nl_sym)) {
if (oj_hash_has_key(ropts, oj_array_nl_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_array_nl_sym))) {
copts->dump_opts.array_size = 0;
*copts->dump_opts.array_nl = '\0';
Expand Down Expand Up @@ -914,23 +921,23 @@ void oj_parse_options(VALUE ropts, Options copts) {
} else if (Qfalse == v) {
copts->escape_mode = JSONEsc;
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_hash_class_sym)) {
if (oj_hash_has_key(ropts, oj_hash_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_hash_class_sym))) {
copts->hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
copts->hash_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_object_class_sym)) {
if (oj_hash_has_key(ropts, oj_object_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_object_class_sym))) {
copts->hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
copts->hash_class = v;
}
}
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, oj_array_class_sym)) {
if (oj_hash_has_key(ropts, oj_array_class_sym)) {
if (Qnil == (v = rb_hash_lookup(ropts, oj_array_class_sym))) {
copts->array_class = Qnil;
} else {
Expand All @@ -939,7 +946,7 @@ void oj_parse_options(VALUE ropts, Options copts) {
}
}
oj_parse_opt_match_string(&copts->str_rx, ropts);
if (Qtrue == rb_funcall(ropts, oj_has_key_id, 1, ignore_sym)) {
if (oj_hash_has_key(ropts, ignore_sym)) {
xfree(copts->ignore);
copts->ignore = NULL;
if (Qnil != (v = rb_hash_lookup(ropts, ignore_sym))) {
Expand Down Expand Up @@ -1785,7 +1792,6 @@ void Init_oj() {
oj_file_id = rb_intern("file?");
oj_fileno_id = rb_intern("fileno");
oj_ftype_id = rb_intern("ftype");
oj_has_key_id = rb_intern("has_key?");
oj_hash_end_id = rb_intern("hash_end");
oj_hash_key_id = rb_intern("hash_key");
oj_hash_set_id = rb_intern("hash_set");
Expand Down
2 changes: 1 addition & 1 deletion ext/oj/oj.h
Expand Up @@ -245,6 +245,7 @@ extern VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len)
extern VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len);
extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len);

extern bool oj_hash_has_key(VALUE hash, VALUE key);
extern void oj_parse_options(VALUE ropts, Options copts);

extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
Expand Down Expand Up @@ -327,7 +328,6 @@ extern ID oj_exclude_end_id;
extern ID oj_file_id;
extern ID oj_fileno_id;
extern ID oj_ftype_id;
extern ID oj_has_key_id;
extern ID oj_hash_end_id;
extern ID oj_hash_key_id;
extern ID oj_hash_set_id;
Expand Down

0 comments on commit ad67f5e

Please sign in to comment.