Skip to content

Commit

Permalink
Improve Oj.load performance (#680)
Browse files Browse the repository at this point in the history
When use non-frozen string as hash key with rb_hash_aset(), it will duplicate and freeze the string internally.

```c
static int
hash_aset_str(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
{
    if (!existing && !RB_OBJ_FROZEN(*key)) {
	*key = rb_hash_key_str(*key);
    }
    return hash_aset(key, val, arg, existing);
}
```
Refer: https://github.com/ruby/ruby/blob/bda56a03a625793cb3fd110458c3f7323d73705e/hash.c#L2890-L2897

To avoid duplicate and freeze, this patch will give a frozen string in rb_hash_aset().

FYI)
If you use string as hash key, hash object always might have frozen string as key.

```
irb(main):001:0> hash = { "foo" => 42, bar: 55 }
=> {"foo"=>42, :bar=>55}
irb(main):002:0> hash.keys[0].frozen?
=> true
irb(main):003:0> hash.keys[1].frozen?
=> true
```

This patch has same approch with flori/json#345

−               | before   | after    | result
--               | --       | --       | --
Oj.load          | 335.122k | 422.081k | 1.26x

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

### Before
```
Warming up --------------------------------------
             Oj.load    33.829k i/100ms
Calculating -------------------------------------
             Oj.load    335.122k (± 0.9%) i/s -      1.691M in   5.047682s
```

### After
```
Warming up --------------------------------------
             Oj.load    42.573k i/100ms
Calculating -------------------------------------
             Oj.load    422.081k (± 0.5%) i/s -      2.129M in   5.043373s
```

### 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.report('Oj.load') { Oj.load(json) }
end
```
  • Loading branch information
Watson1978 committed Aug 5, 2021
1 parent c85059c commit 2f8cf2b
Show file tree
Hide file tree
Showing 3 changed files with 4 additions and 0 deletions.
1 change: 1 addition & 0 deletions ext/oj/object.c
Expand Up @@ -70,6 +70,7 @@ static VALUE calc_hash_key(ParseInfo pi, Val kval, char k1) {
}
}
#endif
OBJ_FREEZE(rkey);
return rkey;
}

Expand Down
2 changes: 2 additions & 0 deletions ext/oj/strict.c
Expand Up @@ -44,6 +44,7 @@ VALUE oj_calc_hash_key(ParseInfo pi, Val parent) {
if (Yes == pi->options.sym_key) {
rkey = rb_str_intern(rkey);
}
OBJ_FREEZE(rkey);
return rkey;
}
VALUE *slot;
Expand All @@ -64,6 +65,7 @@ VALUE oj_calc_hash_key(ParseInfo pi, Val parent) {
rb_gc_register_address(slot);
}
}
OBJ_FREEZE(rkey);
return rkey;
}

Expand Down
1 change: 1 addition & 0 deletions ext/oj/wab.c
Expand Up @@ -318,6 +318,7 @@ static VALUE calc_hash_key(ParseInfo pi, Val parent) {
*slot = rkey;
rb_gc_register_address(slot);
}
OBJ_FREEZE(rkey);
return rkey;
}

Expand Down

0 comments on commit 2f8cf2b

Please sign in to comment.