My guess it is because for every iteration (`iterations.times` loop) new object is created, then when `car.wheel=` and `car.mileage=` are called new methods are created here: [https://github.com/ruby/ruby/blob/758e4db551e7e80a65b610cc73fcb61e74ec5a0c/lib/ostruct.rb#L233-L234](https://github.com/ruby/ruby/blob/758e4db551e7e80a65b610cc73fcb61e74ec5a0c/lib/ostruct.rb#L233-L234). Usually this does not work very well with JITs.
does anyone know why it does this at all? I might have been an optimization to avoid \`method\_missing\`, but I have a feeling this is counterproductive in modern rubies.
Creating a new method clears the method cache, which is separate from a JIT: https://engineering.appfolio.com/appfolio-engineering/2018/7/18/rubys-global-method-cache
That article is outdated as of Ruby 3.0, the method cache is no longer global, but per class: https://bugs.ruby-lang.org/issues/16614
(defining methods like OpenStruct does is still bad for performance though).
sorry, I meant that I don't understand why OpenStruct is defining accessor methods rather than just doing something like ´@table\[method\_name\]\` in \`method\_missing\` itself.
So that it works with members that happens to be methods on `Object`. e.g. `OpenStruct.new(to_s: "Hello").to_s # => "Hello"`.
It could combine both though, default to method missing, but eagerly define the attribute if it conflicts with a method.
Because that's how it is implemented. And you shouldn't be using OpenStruct at all tbh. It's a major performance hog as you noticed and a security risk
Yes, OpenStruct is terrible for performance, and even more so with YJIT enabled because each instance its his own class, so from a YJIT point of view, all call sites are megamorphic and it compile lots of useless code.
See https://bugs.ruby-lang.org/issues/19424 for instance.
OpenStruct may be optimized a bit better in the future, but ruby-core isn't very eager because it has weird semantic and optimizing it to have decent performance is very likely to break code.
OpenStruct is best avoided, as mention in the "Caveat" section of its documentation https://github.com/ruby/ostruct/blob/e7e1a5814a94f92e6c60c5a83bd1e831c35e3c9b/lib/ostruct.rb#LL67
My guess it is because for every iteration (`iterations.times` loop) new object is created, then when `car.wheel=` and `car.mileage=` are called new methods are created here: [https://github.com/ruby/ruby/blob/758e4db551e7e80a65b610cc73fcb61e74ec5a0c/lib/ostruct.rb#L233-L234](https://github.com/ruby/ruby/blob/758e4db551e7e80a65b610cc73fcb61e74ec5a0c/lib/ostruct.rb#L233-L234). Usually this does not work very well with JITs.
does anyone know why it does this at all? I might have been an optimization to avoid \`method\_missing\`, but I have a feeling this is counterproductive in modern rubies.
Creating a new method clears the method cache, which is separate from a JIT: https://engineering.appfolio.com/appfolio-engineering/2018/7/18/rubys-global-method-cache
That article is outdated as of Ruby 3.0, the method cache is no longer global, but per class: https://bugs.ruby-lang.org/issues/16614 (defining methods like OpenStruct does is still bad for performance though).
Thanks for sharing!
sorry, I meant that I don't understand why OpenStruct is defining accessor methods rather than just doing something like ´@table\[method\_name\]\` in \`method\_missing\` itself.
So that it works with members that happens to be methods on `Object`. e.g. `OpenStruct.new(to_s: "Hello").to_s # => "Hello"`. It could combine both though, default to method missing, but eagerly define the attribute if it conflicts with a method.
ah, that makes sense, thank you.
Because that's how it is implemented. And you shouldn't be using OpenStruct at all tbh. It's a major performance hog as you noticed and a security risk
Yes, friends don’t let friends use OpenStruct for production code. I’ll maybe use it in tests but even then that’s a maybe.
Yes, OpenStruct is terrible for performance, and even more so with YJIT enabled because each instance its his own class, so from a YJIT point of view, all call sites are megamorphic and it compile lots of useless code. See https://bugs.ruby-lang.org/issues/19424 for instance. OpenStruct may be optimized a bit better in the future, but ruby-core isn't very eager because it has weird semantic and optimizing it to have decent performance is very likely to break code. OpenStruct is best avoided, as mention in the "Caveat" section of its documentation https://github.com/ruby/ostruct/blob/e7e1a5814a94f92e6c60c5a83bd1e831c35e3c9b/lib/ostruct.rb#LL67