-
Notifications
You must be signed in to change notification settings - Fork 27
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
Could we simplify OpenStruct to not define any methods, just method_missing + Hash and < BasicObject? #51
Comments
Inheriting from BasicObject would allow "overriding" Object methods by simply not having them, but that's probably too incompatible as it means Kernel's public instance methods wouldn't be defined on OpenStruct like If we inherit from Object then it doesn't allow overriding them anymore, because method_missing is of course only called if the method doesn't already exists. |
I tried it as POC changes, and with those changes the tests pass. Here it is with OpenStruct < Object, which doesn't handle overriding Kernel methods: And here it is with OpenStruct < BasicObject, which is more compatible (handles overriding Kernel methods but not overriding OpenStruct methods) and delegates to Kernel in method_missing: Probably the most interesting parts is the changes to |
Thanks for raising this issue. Personally, I am completey un-sympathetic to performance issues about I am also un-sympathetic to simplifying the code at the expense of compatibility and functionality. |
I benchmarked master...eregon:ostruct:simplify-basic-object, since that's more compatible and probably could be made 100% compatible easily (by moving OpenStruct methods to some Procs or explicit handling in The main point of this change is performance, but also IMO it simplifies the code and logic significantly. puts RUBY_DESCRIPTION
require 'benchmark/ips'
require 'ostruct'
class MyClass
attr_accessor :a, :b, :c
def initialize(a:, b:, c:)
@a = a
@b = b
@c = c
end
end
Benchmark.ips do |x|
obj = nil
result = 0
x.report("OpenStruct") do
obj = OpenStruct.new(a: 4, b: 7, c: 9)
# obj.a = 4
# obj.b = 7
# obj.c = 9
result = obj.a * obj.b * obj.c
end
x.report("MyClass") do
obj = MyClass.new(a: 4, b: 7, c: 9)
result = obj.a * obj.b * obj.c
end
x.compare!
end In (iterations per second, times slower than Class):
Here is the full output: https://gist.github.com/eregon/b65909bd1b9a7c45dc1e3c58df194e85?permalink_comment_id=4496630#gistcomment-4496630 Which makes my point about OpenStruct being horribly expensive, and "even more so on more optimizing Ruby implementations." very clear. We need to do something here, the performance of OpenStruct is too terrible, especially on faster Rubies. Also https://twitter.com/coolprobn/status/1632243667531624448 |
I updated the benchmark to force the allocation, using the variant in https://gist.github.com/eregon/b65909bd1b9a7c45dc1e3c58df194e85?permalink_comment_id=4496630#gistcomment-4496630. |
One thing that came up discussing this on the CRuby Slack is inheriting from BasicObject has the problem that methods defined in subclasses of OpenStruct would need e.g
So probably we shouldn't use BasicObject but make our own "BlankSlate". |
OpenStruct design cause it to have non-local performance impact, especially with JIT compilers. See ruby/ostruct#51 for full context, and it Ruby 3.3 OpenStruct usage raise a performance warnings: ``` gems/state_machines-audit_trail-2.0.2/lib/state_machines/audit_trail/transition_auditing.rb:38: warning: OpenStruct use is discouraged for performance reasons ``` In this case I don't really see any advantage of OpenStruct over a simple PORO.
OpenStruct design cause it to have non-local performance impact, especially with JIT compilers. See ruby/ostruct#51 for full context, and it Ruby 3.3 OpenStruct usage raise a performance warnings: ``` gems/state_machines-audit_trail-2.0.2/lib/state_machines/audit_trail/transition_auditing.rb:38: warning: OpenStruct use is discouraged for performance reasons ``` In this case I don't really see any advantage of OpenStruct over a simple PORO.
OpenStruct design cause it to have non-local performance impact, especially with JIT compilers. See ruby/ostruct#51 for full context, and it Ruby 3.3 OpenStruct usage raise a performance warnings: ``` gems/state_machines-audit_trail-2.0.2/lib/state_machines/audit_trail/transition_auditing.rb:38: warning: OpenStruct use is discouraged for performance reasons ``` In this case I don't really see any advantage of OpenStruct over a simple PORO.
OpenStruct design cause it to have non-local performance impact, especially with JIT compilers. See ruby/ostruct#51 for full context, and it Ruby 3.3 OpenStruct usage raise a performance warnings: ``` gems/state_machines-audit_trail-2.0.2/lib/state_machines/audit_trail/transition_auditing.rb:38: warning: OpenStruct use is discouraged for performance reasons ``` In this case I don't really see any advantage of OpenStruct over a simple PORO.
See https://bugs.ruby-lang.org/issues/19424#note-11
The idea comes from oracle/truffleruby#2702 (comment)
I think this would be a nice simplification, and would be more intuitive performance-wise.
Defining methods dynamically is extremely expensive (also even just forcing a singleton class is expensive as well), even more so on more optimizing Ruby implementations.
Slightly slower for all access but no hidden costs on defining new members/new instance seems better.
Any thought on that?
I should be able to provide a POC PR, just asking if there is known compatibility issue with that approach.
The text was updated successfully, but these errors were encountered: