-
-
Notifications
You must be signed in to change notification settings - Fork 5
Address perf #62
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
Address perf #62
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PropsTemplate is fast, but can always be improved. This commit reduces the
amount of object allocations by removing cloning of options passed to an array.
Cloning options was convienent as it allows us somewhere to store multifeched
objects, expand procs, and store templates for faster template lookups, but
this can be expensive.
This commit removes that approach and instead stores multifecthed objected in
the cache object, and mutates the options for one thing (to store a template
for arrays). This change made Props::Base beat Panko making it the faster
JSON builder in ruby land. Props::Template itself benefited as well, and now
outpaces alba and turbostreamer.
Running benchmark with the following configuration:
YJIT: enabled
Oj.optimize_rails: enabled
-- create_table(:posts, {force: true})
-> 0.0070s
-- create_table(:comments, {force: true})
-> 0.0002s
-- create_table(:users, {force: true})
-> 0.0006s
Checking outputs...
=== WITH GC ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
alba 98.000 i/100ms
alba_inline 3.000 i/100ms
panko 137.000 i/100ms
props_template 103.000 i/100ms
props_template_with_set
120.000 i/100ms
props_base_with_extensions
121.000 i/100ms
turbostreamer 93.000 i/100ms
props_base 138.000 i/100ms
props_base_json 131.000 i/100ms
Calculating -------------------------------------
alba 961.436 (± 0.8%) i/s (1.04 ms/i) - 4.900k in 5.096913s
alba_inline 12.574 (±23.9%) i/s (79.53 ms/i) - 60.000 in 5.053625s
panko 1.348k (± 1.0%) i/s (741.75 μs/i) - 6.850k in 5.081588s
props_template 1.024k (± 0.6%) i/s (976.52 μs/i) - 5.150k in 5.029253s
props_template_with_set
1.184k (± 1.6%) i/s (844.87 μs/i) - 6.000k in 5.070565s
props_base_with_extensions
1.195k (± 1.0%) i/s (836.99 μs/i) - 6.050k in 5.064289s
turbostreamer 917.855 (± 0.7%) i/s (1.09 ms/i) - 4.650k in 5.066375s
props_base 1.357k (± 0.8%) i/s (736.83 μs/i) - 6.900k in 5.084468s
props_base_json 1.298k (± 0.8%) i/s (770.67 μs/i) - 6.550k in 5.048256s
Comparison:
props_base: 1357.2 i/s
panko: 1348.2 i/s - same-ish: difference falls within error
props_base_json: 1297.6 i/s - 1.05x slower
props_base_with_extensions: 1194.8 i/s - 1.14x slower
props_template_with_set: 1183.6 i/s - 1.15x slower
props_template: 1024.0 i/s - 1.33x slower
alba: 961.4 i/s - 1.41x slower
turbostreamer: 917.9 i/s - 1.48x slower
alba_inline: 12.6 i/s - 107.93x slower
=== WITHOUT GC ===
ruby 3.4.8 (2025-12-17 revision 995b59f666) +YJIT +PRISM [arm64-darwin25]
Warming up --------------------------------------
alba 68.000 i/100ms
alba_inline 1.000 i/100ms
panko 132.000 i/100ms
props_template 77.000 i/100ms
props_template_with_set
118.000 i/100ms
props_base_with_extensions
127.000 i/100ms
turbostreamer 97.000 i/100ms
props_base 147.000 i/100ms
props_base_json 137.000 i/100ms
Calculating -------------------------------------
alba 588.791 (±48.7%) i/s (1.70 ms/i) - 2.380k in 5.174567s
alba_inline 6.598 (± 0.0%) i/s (151.56 ms/i) - 33.000 in 5.023939s
panko 1.076k (±22.2%) i/s (929.07 μs/i) - 5.148k in 5.035475s
props_template 708.005 (±45.1%) i/s (1.41 ms/i) - 2.926k in 5.147218s
props_template_with_set
873.069 (±45.4%) i/s (1.15 ms/i) - 3.422k in 5.107466s
props_base_with_extensions
924.202 (±43.3%) i/s (1.08 ms/i) - 3.683k in 5.196687s
turbostreamer^[[C 732.770 (±39.3%) i/s (1.36 ms/i) - 2.910k in 5.017891s
props_base 1.058k (±45.0%) i/s (945.05 μs/i) - 3.969k in 5.158039s
props_base_json 998.945 (±44.3%) i/s (1.00 ms/i) - 3.836k in 5.483344s
Comparison:
panko: 1076.3 i/s
props_base: 1058.1 i/s - same-ish: difference falls within error
props_base_json: 998.9 i/s - same-ish: difference falls within error
props_base_with_extensions: 924.2 i/s - same-ish: difference falls within error
props_template_with_set: 873.1 i/s - same-ish: difference falls within error
turbostreamer: 732.8 i/s - same-ish: difference falls within error
props_template: 708.0 i/s - same-ish: difference falls within error
alba: 588.8 i/s - same-ish: difference falls within error
alba_inline: 6.6 i/s - 163.14x slower
Calculating -------------------------------------
alba 833.929k memsize ( 0.000 retained)
9.804k objects ( 0.000 retained)
6.000 strings ( 0.000 retained)
alba_inline 2.817M memsize ( 0.000 retained)
22.204k objects ( 0.000 retained)
38.000 strings ( 0.000 retained)
panko 259.178k memsize ( 0.000 retained)
3.033k objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
props_template 457.698k memsize ( 0.000 retained)
7.718k objects ( 0.000 retained)
6.000 strings ( 0.000 retained)
props_template_with_set
457.698k memsize ( 0.000 retained)
7.718k objects ( 0.000 retained)
6.000 strings ( 0.000 retained)
props_base_with_extensions
457.578k memsize ( 0.000 retained)
7.716k objects ( 0.000 retained)
6.000 strings ( 0.000 retained)
turbostreamer 641.720k memsize ( 0.000 retained)
9.741k objects ( 0.000 retained)
33.000 strings ( 0.000 retained)
props_base 428.858k memsize ( 0.000 retained)
7.406k objects ( 0.000 retained)
6.000 strings ( 0.000 retained)
props_base_json 642.985k memsize ( 0.000 retained)
5.308k objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
Comparison:
panko: 259178 allocated
props_base: 428858 allocated - 1.65x more
props_base_with_extensions: 457578 allocated - 1.77x more
props_template: 457698 allocated - 1.77x more
props_template_with_set: 457698 allocated - 1.77x more
turbostreamer: 641720 allocated - 2.48x more
props_base_json: 642985 allocated - 2.48x more
alba: 833929 allocated - 3.22x more
alba_inline: 2816849 allocated - 10.87x more
Ruby version: 3.4.8
Oj version: 3.16.13
For large caches, this prevents split from reading the entire string.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Performance improvements
PropsTemplate is fast, but can always be improved. This commit reduces the
amount of object allocations by removing cloning of options passed to an array.
Cloning options was convienent as it allows us somewhere to store multifeched
objects, expand procs, and store templates for faster template lookups, but
this can be expensive.
This commit removes that approach and instead stores multifecthed objected in
the cache object, and mutates the options for one thing (to store a template
for arrays). This change made Props::Base beat Panko making it the faster
JSON builder in ruby land. Props::Template itself benefited as well, and now
outpaces alba and turbostreamer.