-
-
Notifications
You must be signed in to change notification settings - Fork 925
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
RubyBasicObject
is Redundant (in light of RubyObject
) and Breaks Inlining
#4763
Comments
I am not against the general idea (rb was bolted onto our impl during 1.9 timeframe) but I think there may be at least one external Java library providing blankslate-like behavior constructing raw RubyBasicObjects. So if we do this then we need to make a new API for making BasicObject instances (which should be super simple) also a nice way to check if something is a BasicObject (from Java) and then deprecate this. Java integration also internally uses these for java packages so looking at how it is used there might be useful (I think it probably is the two things I mentioned above). Having to deprecate when it involves a constructor I think would be hard to work around as far as getting the quick gain you see in JITWatch. Possibly we could try class RubyBasicObject implements IRubyObject to maintain backwards compat? Seems likely to run aground but maybe that would be a path forward without waiting for major release. The other question to ask is this analogue worth having? RubyBasicObject -> BasicObject |
currently not - seems that we're still able to HACK around using the blank-state-wrapper (for Java packages - so that methods are not triggered but resolve as package names) while wrapping a package instance in a module instead of |
This is definitely a tricky problem to work around. The 9-deep inlining is specific to Hotspot, so that may not have the same effect under e.g. Graal or J9. But Hotspot is obviously going to be the widest-deployed JVM for a long time. This is particularly problematic for some of the heaviest-used objects in JRuby, like numbers. For example, the path to construct a Fixnum object needs to go through at least six constructors (RubyFixnum < RubyInteger < RubyNumeric < RubyObject < RubyBasicObject < Object) and typically will go through at least a couple overloads, a factory method, and usually a Ruby method like "+" or "-" that has multiple layers. It's quite easy then to exceed 9 frames. Because of the way Java handles constructors, the only way to remove any of these levels is to remove the class (you can't skip your immediate parent class's constructor). So we're back to your suggestion of removing -- in some way -- RubyBasicObject. That would be an API challenge since this type has existed for many years and there are likely many libraries that reference it as a class (so we can't simply make it an interface). The other option we have not done recently is to audit all constructor paths for the key core Ruby types to ensure they're as short as possible; this may mean duplicating constructor or factory methods to avoid extra levels of dispatch. I'll take a quick look at that angle. |
Bleh, the removal or skipping of factory methods is also problematic, because the factory methods check the width of the fixnum cache. There's no way to have a constructor call return a cached object, and no way to have a factory method skip the constructor if it needs to make a new object. So any of these factory methods we want to eliminate would have to move the cache hit up to the caller. |
Ok, for a simple numeric benchmark, reducing the number of frames by one or two doesn't appear to help Fixnum creation a great deal. It may help more in cases where there's no conditional logic wrapping the construction (e.g. temporary Array wrappers for args), since those cases will fare better in Hotspot's escape analysis. |
There's another possible option: reverse the relationship of RubyObject and RubyBasicObject. All classes in Ruby declared through normal means will extend Object or some subclass of it, meaning 99% of objects in the system will be or descend from RubyObject. The remaining small percentage of objects will be RubyBasicObject. All cases where the superclass is BasicObject will be RubyBasicObject instances. All other cases will be RubyObject. To me this makes the two base types somewhat disjoint. There are instanceof checks that could be problematic; e.g. RubyBasicObject is currently an Object but not a RubyObject. With this change, that would reverse; RubyObject would not longer be a RubyBasicObject, but RubyBasicObject would be a RubyObject. Another idea: make RubyBasicObject function as a standalone type, but don't use it normally for objects in Ruby based on BasicObject. That would allow Java APIs to continue to work with RubyBasicObject, but internally we would treat that and RubyObject+BasicObject metaclass as the same thing. |
There's another value in providing special roots for the hierarchy: immediate types could be modified to use alternative instance variable stores (or none at all) saving 32-64 bits of header space on those objects. This could also be applied to flags, if we had a base class that always made a frozen, immediate object and a base class that had configurable flags. |
Ok, I did some experimentation along these lines. I think this is only really feasible to tackle along with a move to Java 8, since we get default interface methods. Default methods would help us pull interfaces out for some of these core types that don't really need standalone classes, like RubyNumeric and RubyInteger. RubyNumeric, for example, could become an "RNumeric" interface with all the standard impls as default methods. Then anything that currently extends RubyNumeric could simply extend RubyObject and implement RNumeric. This would make it possible to arbitrarily flatten almost any hierarchy. Targeted use for immediate numbers could reduce the size of floats and fixnums by 2-4 fields, potentially saving many bytes per object. Of course there's a lot of challenges to making this move:
I think Java 8 is a prerequisite for this work in any case. |
It looks like
RubyBasicObject
is only extended byRubyObject
and doesn't have any other valid standalone use cases (all thestatic
methods on it could just as well live onRubyObject
I guess?)The reason this may be interesting is, that one of the constructors (the one with the ObjectSpace action) cannot be inlined fully at the default inlining depth of
9
.Jitwatch says:
Not sure what the quantitative impact is of this, but if I up the inlining depth to
12
it seems the red-black benchmark gets speed up by~5%
.Maybe there is a quick and easy gain possible here by simply merging
RubyBasicObject
intoRubyObject
(or the other way around)?The text was updated successfully, but these errors were encountered: