Skip to content

Various JSON.generate optimizations #615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 17, 2024
Merged

Conversation

casperisfine
Copy link

@casperisfine casperisfine commented Oct 17, 2024

Extracted from #562, this isn't the entirety of the PR, some of the changes are harder to cherry pick and I'll try to get them in followups. The main one missing being a81ec47

Before:

== Encoding small nested array (121 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json    91.687k i/100ms
                  oj   205.309k i/100ms
           rapidjson   161.648k i/100ms
Calculating -------------------------------------
                json    941.965k (± 1.4%) i/s    (1.06 μs/i) -      4.768M in   5.062573s
                  oj      2.138M (± 1.2%) i/s  (467.82 ns/i) -     10.881M in   5.091254s
           rapidjson      1.678M (± 1.9%) i/s  (596.04 ns/i) -      8.406M in   5.011931s

Comparison:
                json:   941964.8 i/s
                  oj:  2137586.5 i/s - 2.27x  faster
           rapidjson:  1677737.1 i/s - 1.78x  faster


== Encoding small hash (65 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   141.737k i/100ms
                  oj   676.871k i/100ms
           rapidjson   373.266k i/100ms
Calculating -------------------------------------
                json      1.491M (± 1.0%) i/s  (670.78 ns/i) -      7.512M in   5.039463s
                  oj      7.226M (± 1.4%) i/s  (138.39 ns/i) -     36.551M in   5.059475s
           rapidjson      3.729M (± 2.2%) i/s  (268.15 ns/i) -     18.663M in   5.007182s

Comparison:
                json:  1490798.2 i/s
                  oj:  7225766.2 i/s - 4.85x  faster
           rapidjson:  3729192.2 i/s - 2.50x  faster


== Encoding twitter.json (466906 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   118.000 i/100ms
                  oj   230.000 i/100ms
           rapidjson   106.000 i/100ms
Calculating -------------------------------------
                json      1.194k (± 1.2%) i/s  (837.76 μs/i) -      6.018k in   5.042338s
                  oj      2.334k (± 1.6%) i/s  (428.47 μs/i) -     11.730k in   5.027262s
           rapidjson      1.118k (± 4.7%) i/s  (894.28 μs/i) -      5.618k in   5.032519s

Comparison:
                json:     1193.7 i/s
                  oj:     2333.9 i/s - 1.96x  faster
           rapidjson:     1118.2 i/s - 1.07x  slower


== Encoding citm_catalog.json (500298 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json    86.000 i/100ms
                  oj   130.000 i/100ms
           rapidjson    99.000 i/100ms
Calculating -------------------------------------
                json    872.375 (± 1.9%) i/s    (1.15 ms/i) -      4.386k in   5.029679s
                  oj      1.301k (± 2.5%) i/s  (768.48 μs/i) -      6.630k in   5.098077s
           rapidjson    986.414 (± 1.9%) i/s    (1.01 ms/i) -      4.950k in   5.019950s

Comparison:
                json:      872.4 i/s
                  oj:     1301.3 i/s - 1.49x  faster
           rapidjson:      986.4 i/s - 1.13x  faster


== Encoding canada.json (2090234 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json     1.000 i/100ms
                  oj     3.000 i/100ms
           rapidjson     1.000 i/100ms
Calculating -------------------------------------
                json     19.789 (± 0.0%) i/s   (50.53 ms/i) -     99.000 in   5.003952s
                  oj     30.697 (± 0.0%) i/s   (32.58 ms/i) -    156.000 in   5.082418s
           rapidjson     19.116 (± 0.0%) i/s   (52.31 ms/i) -     96.000 in   5.023745s

Comparison:
                json:       19.8 i/s
                  oj:       30.7 i/s - 1.55x  faster
           rapidjson:       19.1 i/s - 1.04x  slower


== Encoding many #to_json calls (2661 bytes)
oj does not match expected output. Skipping
rapidjson unsupported (Invalid object key type: Object)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json     2.022k i/100ms
Calculating -------------------------------------
                json     22.241k (± 1.3%) i/s   (44.96 μs/i) -    111.210k in   5.001111s

After:

== Encoding small nested array (121 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   145.767k i/100ms
                  oj   209.915k i/100ms
           rapidjson   167.157k i/100ms
Calculating -------------------------------------
                json      1.499M (± 1.2%) i/s  (666.93 ns/i) -      7.580M in   5.056036s
                  oj      2.134M (± 1.7%) i/s  (468.53 ns/i) -     10.706M in   5.017418s
           rapidjson      1.723M (± 2.1%) i/s  (580.47 ns/i) -      8.692M in   5.047929s

Comparison:
                json:  1499413.4 i/s
                  oj:  2134324.3 i/s - 1.42x  faster
           rapidjson:  1722754.0 i/s - 1.15x  faster


== Encoding small hash (65 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   194.424k i/100ms
                  oj   677.724k i/100ms
           rapidjson   366.156k i/100ms
Calculating -------------------------------------
                json      2.076M (± 1.5%) i/s  (481.61 ns/i) -     10.499M in   5.057577s
                  oj      7.115M (± 1.7%) i/s  (140.55 ns/i) -     35.919M in   5.050125s
           rapidjson      3.848M (± 3.0%) i/s  (259.90 ns/i) -     19.406M in   5.048376s

Comparison:
                json:  2076365.8 i/s
                  oj:  7114759.1 i/s - 3.43x  faster
           rapidjson:  3847588.8 i/s - 1.85x  faster


== Encoding twitter.json (466906 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   146.000 i/100ms
                  oj   221.000 i/100ms
           rapidjson   112.000 i/100ms
Calculating -------------------------------------
                json      1.465k (± 2.3%) i/s  (682.81 μs/i) -      7.446k in   5.086936s
                  oj      2.305k (± 2.3%) i/s  (433.85 μs/i) -     11.713k in   5.084603s
           rapidjson      1.083k (± 2.7%) i/s  (923.12 μs/i) -      5.488k in   5.069470s

Comparison:
                json:     1464.5 i/s
                  oj:     2304.9 i/s - 1.57x  faster
           rapidjson:     1083.3 i/s - 1.35x  slower


== Encoding citm_catalog.json (500298 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json   111.000 i/100ms
                  oj   128.000 i/100ms
           rapidjson    96.000 i/100ms
Calculating -------------------------------------
                json      1.126k (± 2.7%) i/s  (887.78 μs/i) -      5.661k in   5.029478s
                  oj      1.283k (± 2.7%) i/s  (779.34 μs/i) -      6.528k in   5.091464s
           rapidjson    986.411 (± 1.5%) i/s    (1.01 ms/i) -      4.992k in   5.062001s

Comparison:
                json:     1126.4 i/s
                  oj:     1283.1 i/s - 1.14x  faster
           rapidjson:      986.4 i/s - 1.14x  slower


== Encoding canada.json (2090234 bytes)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json     1.000 i/100ms
                  oj     3.000 i/100ms
           rapidjson     1.000 i/100ms
Calculating -------------------------------------
                json     19.740 (± 0.0%) i/s   (50.66 ms/i) -     99.000 in   5.016553s
                  oj     30.289 (± 6.6%) i/s   (33.02 ms/i) -    153.000 in   5.081497s
           rapidjson     19.171 (± 0.0%) i/s   (52.16 ms/i) -     96.000 in   5.008856s

Comparison:
                json:       19.7 i/s
                  oj:       30.3 i/s - 1.53x  faster
           rapidjson:       19.2 i/s - 1.03x  slower


== Encoding many #to_json calls (2661 bytes)
oj does not match expected output. Skipping
rapidjson unsupported (Invalid object key type: Object)
ruby 3.4.0preview2 (2024-10-07 master 32c733f57b) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
                json     2.267k i/100ms
Calculating -------------------------------------
                json     22.548k (± 1.3%) i/s   (44.35 μs/i) -    113.350k in   5.027945s

FYI @mame

mame added 8 commits October 17, 2024 10:14
It is safe to use `RARRAY_AREF` here because no Ruby code is executed
between `RARRAY_LEN` and `RARRAY_AREF`.

This speeds up `JSON.generate` by about 4% in a benchmark.
Dispatching based on Ruby's VALUE structure is more efficient than
simply cascaded "if ... else if ..." checks.

This speeds up `JSON.generate` by about 5% in a benchmark.
... instead of `generate_json`.

Since the object key is already confirmed to be a string, using a
generic dispatch function brings an unnecessary overhead.

This speeds up `JSON.generate` by about 3% in a benchmark.
The purpose of this change is to exploit `fbuffer_append_char` that is
faster than `fbuffer_append`.

`array_delim` was a buffer that concatenated a single comma with
`array_nl`. However, in the typical use case (`JSON.generate(data)`),
`array_nl` is empty. This means that `array_delim` was a
single-character buffer in many cases.

`fbuffer_append(buffer, array_delim)` used `memcpy` to copy one byte,
which was not so efficient.
Rather, this change uses `fbuffer_append_char(buffer, ',')` and then
`fbuffer_append(buffer, array_nl)` only when `array_nl` is not NULL.

This speeds up `JSON.generate` by about 9% in a benchmark.
This speeds up `JSON.generate` by about 4% in a benchmark
Also, remove static functions that are no longer used.

This speeds up `JSON.generate` by about 5% in a benchmark.
This speeds up `JSON.generate` by about 4% in a benchmark.
This speeds up `JSON.generate` by about 12% in a benchmark.
@byroot byroot merged commit 02f79ef into ruby:master Oct 17, 2024
73 checks passed
@casperisfine casperisfine deleted the many-opts branch October 17, 2024 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants