Skip to content

Avoid defining singleton methods - #62

Open
nevans wants to merge 4 commits into
ruby:masterfrom
nevans:avoid_define_singleton_method
Open

Avoid defining singleton methods#62
nevans wants to merge 4 commits into
ruby:masterfrom
nevans:avoid_define_singleton_method

Conversation

@nevans

@nevansnevans commented Jul 15, 2024

Copy link
Copy Markdown

This PR only creates new singleton methods when they are needed for overwriting an existing instance method. It should be mostly backward-compatible with the current OpenStruct implementation, but I suspect there are some minor incompatibilities (which may be significant).

I believe this should generally be a significant performance improvement. I'm guessing that OpenStruct objects are generally ephemeral and their attributes are generally read fewer than a dozen times on average. In my benchmarks, that wasn't enough for the benefits of defining singleton methods to outweigh the costs. All of the benchmarks I created, except for 100x reads, showed the same or better performance.

And that's mostly ignoring any non-local costs to constantly busting method caches. This PR should almost completely remove that cost.

$ benchmark-driver benchmarks/ostruct.yml
Warming up --------------------------------------
new 7.047k i/s - 7.711k times in 1.094212s (141.90μs/i)
attr_write_read 4.904k i/s - 4.910k times in 1.001289s (203.93μs/i)
index_write_attr_read 6.346k i/s - 6.963k times in 1.097167s (157.57μs/i)
null_reads 79.488k i/s - 85.250k times in 1.072495s (12.58μs/i)
10x_reads 6.651k i/s - 7.282k times in 1.094836s (150.35μs/i)
100x_reads 5.136k i/s - 5.150k times in 1.002780s (194.71μs/i)
Calculating -------------------------------------
v0.6.0 local
new 6.938k 33.067k i/s - 21.141k times in 3.047342s 0.639334s
attr_write_read 4.886k 14.021k i/s - 14.711k times in 3.010686s 1.049232s
index_write_attr_read 6.270k 20.225k i/s - 19.039k times in 3.036489s 0.941373s
null_reads 78.351k 77.187k i/s - 238.462k times in 3.043491s 3.089414s
10x_reads 6.518k 13.306k i/s - 19.953k times in 3.061390s 1.499576s
100x_reads 4.454k 2.078k i/s - 15.407k times in 3.459125s 7.414447s
Comparison:
new
local: 33067.2 i/s
v0.6.0: 6937.5 i/s - 4.77x slower
attr_write_read
local: 14020.7 i/s
v0.6.0: 4886.3 i/s - 2.87x slower
index_write_attr_read
local: 20224.7 i/s
v0.6.0: 6270.1 i/s - 3.23x slower
null_reads
v0.6.0: 78351.5 i/s
local: 77186.8 i/s - 1.02x slower
10x_reads
local: 13305.8 i/s
v0.6.0: 6517.6 i/s - 2.04x slower
100x_reads
v0.6.0: 4454.0 i/s
local: 2078.0 i/s - 2.14x slower

@nevans
nevans marked this pull request as ready for review October 9, 2024 20:50
@nevans
nevansforce-pushed the avoid_define_singleton_method branch from a6736d2 to 319c4daCompareMarch 3, 2025 15:57
@nevans
nevansforce-pushed the avoid_define_singleton_method branch from 319c4da to 51a3dd3CompareJuly 14, 2025 14:21
This supports overriding `#methods`.
This is done only for strict backward compatibility. It may not be
needed for practical backward compatibility.
Don't define singleton methods unless they are needed to override
existing methods. Otherwise, just rely on `method_missing` and
`respond_to_missing?`.
All of the benchmarks except for 100x reads show a performance
improvement. If you are reading the data repeatedly, it's still much
better to use Struct or Data.
```
$ benchmark-driver benchmarks/ostruct.yml
Warming up --------------------------------------
new 7.047k i/s - 7.711k times in 1.094212s (141.90μs/i)
attr_write_read 4.904k i/s - 4.910k times in 1.001289s (203.93μs/i)
index_write_attr_read 6.346k i/s - 6.963k times in 1.097167s (157.57μs/i)
null_reads 79.488k i/s - 85.250k times in 1.072495s (12.58μs/i)
10x_reads 6.651k i/s - 7.282k times in 1.094836s (150.35μs/i)
100x_reads 5.136k i/s - 5.150k times in 1.002780s (194.71μs/i)
Calculating -------------------------------------
v0.6.0 local
new 6.938k 33.067k i/s - 21.141k times in 3.047342s 0.639334s
attr_write_read 4.886k 14.021k i/s - 14.711k times in 3.010686s 1.049232s
index_write_attr_read 6.270k 20.225k i/s - 19.039k times in 3.036489s 0.941373s
null_reads 78.351k 77.187k i/s - 238.462k times in 3.043491s 3.089414s
10x_reads 6.518k 13.306k i/s - 19.953k times in 3.061390s 1.499576s
100x_reads 4.454k 2.078k i/s - 15.407k times in 3.459125s 7.414447s
Comparison:
new
local: 33067.2 i/s
v0.6.0: 6937.5 i/s - 4.77x slower
attr_write_read
local: 14020.7 i/s
v0.6.0: 4886.3 i/s - 2.87x slower
index_write_attr_read
local: 20224.7 i/s
v0.6.0: 6270.1 i/s - 3.23x slower
null_reads
v0.6.0: 78351.5 i/s
local: 77186.8 i/s - 1.02x slower
10x_reads
local: 13305.8 i/s
v0.6.0: 6517.6 i/s - 2.04x slower
100x_reads
v0.6.0: 4454.0 i/s
local: 2078.0 i/s - 2.14x slower
```
@nevans
nevansforce-pushed the avoid_define_singleton_method branch from 51a3dd3 to 7f92e3eCompareAugust 5, 2025 14:33
Sign up for freeto 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.

1 participant

@nevans