Skip to content

Commit

Permalink
Optimize parsing option args in JSON.parse
Browse files Browse the repository at this point in the history
This PR will optimize the parsing options in `JSON.parse`.
It looked up one by one to make sure that the supported options were included in passed options.
This approach of looking up the passed hash each time has an overhead.

This PR will apply the same changing as ohler55#683

−               | before   | after    | result
--               | --       | --       | --
Oj.dump          | 518.709k | 528.462k | 1.019x

### Environment
- MacBook Pro (M1 Max, 2021)
- macOS 12.0
- Apple M1 Max
- Ruby 3.0.2

### Before
```
Warming up --------------------------------------
          JSON.parse    51.973k i/100ms
Calculating -------------------------------------
          JSON.parse    518.709k (± 0.3%) i/s -      5.197M in  10.019799s
```

### After
```
Warming up --------------------------------------
          JSON.parse    52.534k i/100ms
Calculating -------------------------------------
          JSON.parse    528.462k (± 0.5%) i/s -      5.306M in  10.040606s
```

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

json =<<-EOF
{
  "$id": "https://example.com/person.schema.json",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Person",
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string",
      "description": "The person's first name."
    },
    "lastName": {
      "type": "string",
      "description": "The person's last name."
    },
    "age": {
      "description": "Age in years which must be equal to or greater than zero.",
      "type": "integer",
      "minimum": 0
    }
  }
}
EOF

Benchmark.ips do |x|
  x.warmup = 10
  x.time = 10

  Oj.mimic_JSON
  x.report('JSON.parse') { JSON.parse(json, symbolize_names: true) }
end
```
  • Loading branch information
Watson1978 committed Nov 6, 2021
1 parent a4efd89 commit 33a5796
Showing 1 changed file with 47 additions and 49 deletions.
96 changes: 47 additions & 49 deletions ext/oj/mimic_json.c
Expand Up @@ -499,6 +499,52 @@ oj_mimic_pretty_generate(int argc, VALUE *argv, VALUE self) {
return mimic_generate_core(2, rargs, &copts);
}

static int parse_options_cb(VALUE k, VALUE v, VALUE info) {
struct _parseInfo *pi = (struct _parseInfo *)info;

if (oj_symbolize_names_sym == k) {
pi->options.sym_key = (Qtrue == v) ? Yes : No;
} else if (oj_quirks_mode_sym == k) {
pi->options.quirks_mode = (Qtrue == v) ? Yes : No;
} else if (oj_create_additions_sym == k) {
pi->options.create_ok = (Qtrue == v) ? Yes : No;
} else if (oj_allow_nan_sym == k) {
pi->options.allow_nan = (Qtrue == v) ? Yes : No;
} else if (oj_hash_class_sym == k) {
if (Qnil == v) {
pi->options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.hash_class = v;
}
} else if (oj_object_class_sym == k) {
if (Qnil == v) {
pi->options.hash_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.hash_class = v;
}
} else if (oj_array_class_sym == k) {
if (Qnil == v) {
pi->options.array_class = Qnil;
} else {
rb_check_type(v, T_CLASS);
pi->options.array_class = v;
}
} else if (oj_decimal_class_sym == k) {
pi->options.compat_bigdec = (oj_bigdecimal_class == v);
} else if (oj_max_nesting_sym == k) {
if (Qtrue == v) {
pi->max_depth = 100;
} else if (Qfalse == v || Qnil == v) {
pi->max_depth = 0;
} else if (T_FIXNUM == rb_type(v)) {
pi->max_depth = NUM2INT(v);
}
}
return ST_CONTINUE;
}

static VALUE mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) {
struct _parseInfo pi;
VALUE ropts;
Expand All @@ -524,59 +570,11 @@ static VALUE mimic_parse_core(int argc, VALUE *argv, VALUE self, bool bang) {
pi.max_depth = 100;

if (Qnil != ropts) {
VALUE v;

if (T_HASH != rb_type(ropts)) {
rb_raise(rb_eArgError, "options must be a hash.");
}
if (Qnil != (v = rb_hash_lookup(ropts, oj_symbolize_names_sym))) {
pi.options.sym_key = (Qtrue == v) ? Yes : No;
}
if (Qnil != (v = rb_hash_lookup(ropts, oj_quirks_mode_sym))) {
pi.options.quirks_mode = (Qtrue == v) ? Yes : No;
}
if (Qnil != (v = rb_hash_lookup(ropts, oj_create_additions_sym))) {
pi.options.create_ok = (Qtrue == v) ? Yes : No;
}
if (Qnil != (v = rb_hash_lookup(ropts, oj_allow_nan_sym))) {
pi.options.allow_nan = (Qtrue == v) ? Yes : No;
}

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 (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 (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 (oj_hash_has_key(ropts, oj_decimal_class_sym)) {
pi.options.compat_bigdec = (oj_bigdecimal_class == rb_hash_lookup(ropts, oj_decimal_class_sym));
}
v = rb_hash_lookup(ropts, oj_max_nesting_sym);
if (Qtrue == v) {
pi.max_depth = 100;
} else if (Qfalse == v || Qnil == v) {
pi.max_depth = 0;
} else if (T_FIXNUM == rb_type(v)) {
pi.max_depth = NUM2INT(v);
}
rb_hash_foreach(ropts, parse_options_cb, (VALUE)&pi);
oj_parse_opt_match_string(&pi.options.str_rx, ropts);
if (Yes == pi.options.create_ok && Yes == pi.options.sym_key) {
rb_raise(rb_eArgError, ":symbolize_names and :create_additions can not both be true.");
Expand Down

0 comments on commit 33a5796

Please sign in to comment.