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

Remove premade field-based DynamicScope and generate instead. #4174

Merged
merged 19 commits into from Sep 30, 2016

Conversation

Projects
None yet
2 participants
@headius
Member

headius commented Sep 23, 2016

Instead of having hand-written DynamicScope subtypes that use
fields instead of an array, this logic generates those subclasses
as needed.

Currently it only overrides the base getValue and setValue methods
and DynamicScope now implements the other previously-abstract
methods by calling those two. This is not optimal for generic
access via those indexed methods, but the expectation is that JIT
can just go after the fields directly. The additional overrides
would likely be useful for the interpreter, though.

See #4167.

@headius

This comment has been minimized.

Show comment
Hide comment
@headius

headius Sep 23, 2016

Member

It might also be possible to implement getValue and setValue using Unsafe rather than switches, since Unsafe could provide direct access to the field without any branching logic.

Member

headius commented Sep 23, 2016

It might also be possible to implement getValue and setValue using Unsafe rather than switches, since Unsafe could provide direct access to the field without any branching logic.

Remove premade field-based DynamicScope and generate instead.
Instead of having hand-written DynamicScope subtypes that use
fields instead of an array, this logic generates those subclasses
as needed.

Currently it only overrides the base getValue and setValue methods
and DynamicScope now implements the other previously-abstract
methods by calling those two. This is not optimal for generic
access via those indexed methods, but the expectation is that JIT
can just go after the fields directly. The additional overrides
would likely be useful for the interpreter, though.

See #4167.
@headius

This comment has been minimized.

Show comment
Hide comment
@headius

headius Sep 23, 2016

Member

I did a prototype of directly accessing fields from the specialized scopes. It worked reasonably well, but there's a few snags. It does not boot properly (because I removed ManyVarsDynamicScope and other classes that are actually still needed), but it does generate the expected code.

https://gist.github.com/headius/2be4be6b1b8bc71f714901519ee6b5b0

  1. Classloading is complicated. JIT code needs to load into a classloader that can see all scopes that code will try to access. Since we can't have multiple one-shot classloaders as parents, we would need to put all specialized scopes into a common classloader. This in turn keeps them from GCing. That may not be a huge issue, though...a given application will always have a finite, probably-small set of scope sizes.
  2. In order to access the fields, all accesses must checkcast. Unknown what impact this has on performance yet. This could be mitigated by knowing the scope types for all accessed variables and doing the cast once. It can also be statically created with the right type if it is the depth zero scope, negating the need for casts.

The comparative bytecode is here: https://gist.github.com/headius/2c0b2459d8e5fb3c69bbaec254295ea8

This should have better performance than any previous mechanism, unless the checkcast is killing us. Just an experiment for now.

Member

headius commented Sep 23, 2016

I did a prototype of directly accessing fields from the specialized scopes. It worked reasonably well, but there's a few snags. It does not boot properly (because I removed ManyVarsDynamicScope and other classes that are actually still needed), but it does generate the expected code.

https://gist.github.com/headius/2be4be6b1b8bc71f714901519ee6b5b0

  1. Classloading is complicated. JIT code needs to load into a classloader that can see all scopes that code will try to access. Since we can't have multiple one-shot classloaders as parents, we would need to put all specialized scopes into a common classloader. This in turn keeps them from GCing. That may not be a huge issue, though...a given application will always have a finite, probably-small set of scope sizes.
  2. In order to access the fields, all accesses must checkcast. Unknown what impact this has on performance yet. This could be mitigated by knowing the scope types for all accessed variables and doing the cast once. It can also be statically created with the right type if it is the depth zero scope, negating the need for casts.

The comparative bytecode is here: https://gist.github.com/headius/2c0b2459d8e5fb3c69bbaec254295ea8

This should have better performance than any previous mechanism, unless the checkcast is killing us. Just an experiment for now.

headius added some commits Sep 23, 2016

Expand specialization of generated DynamicScope.
* Get and set of depth zero, offset = 0..9
* Flip override of sets to void versions
* Add test for the key methods
Tweaks to DynScope generation.
* Limit specialized subclasses to 50 wide.
* Move to a generator class.
* Cache construction logic to StaticScope.
* Cache constructor in StaticScope.
* Reduce duplication in code gen.
* Move offset error to a shared method in generated classes.
* Use a single OneShotClassLoader since we never dereference.
* Misc perf, style tweaks.
@headius

This comment has been minimized.

Show comment
Hide comment
@headius

headius Sep 28, 2016

Member

@subbuss @enebo @kares I'm pretty much done with this PR and would like input and review.

The concerns I had/have are:

  • Generating too many classes. This is mitigated by a limit of 50 in my most recent commit.
  • Cost of construction. This has been mitigated by using MethodHandle instead of reflection and caching that handle in StaticScope, so there's fewer steps from newDynamicScope to having an object in hand.
  • Caching classes statically. This is still done, but since there will never be more than 50 within the classloader that booted JRuby, I am not worried.

The biggest improvement in this PR from beginning to end is that there are now ten offset-specialized get/set methods that are generated for all specialized DynScopes, and the JIT will automatically use them. Adding more requires only implementing them in DynamicScope and ManyVarsDynamicScope and adding their names to the lists in DynamicScopeGenerator. These methods allow heap variable accesses to inline down to a single field read or write with no extra overhead.

Inlining-wise at the JVM level, a given jitted code body will generally have only one StaticScope and will only ever see one DynamicScope type, so every method body should be able to inline logic for its own scope.

I have not revisited the direct field access because I was able to expand the use of offset-specialized methods. However, it might be interesting to see indy-based scope var access, which could specialize beyond the base set of offsets. I have no plans to do this right now.

Member

headius commented Sep 28, 2016

@subbuss @enebo @kares I'm pretty much done with this PR and would like input and review.

The concerns I had/have are:

  • Generating too many classes. This is mitigated by a limit of 50 in my most recent commit.
  • Cost of construction. This has been mitigated by using MethodHandle instead of reflection and caching that handle in StaticScope, so there's fewer steps from newDynamicScope to having an object in hand.
  • Caching classes statically. This is still done, but since there will never be more than 50 within the classloader that booted JRuby, I am not worried.

The biggest improvement in this PR from beginning to end is that there are now ten offset-specialized get/set methods that are generated for all specialized DynScopes, and the JIT will automatically use them. Adding more requires only implementing them in DynamicScope and ManyVarsDynamicScope and adding their names to the lists in DynamicScopeGenerator. These methods allow heap variable accesses to inline down to a single field read or write with no extra overhead.

Inlining-wise at the JVM level, a given jitted code body will generally have only one StaticScope and will only ever see one DynamicScope type, so every method body should be able to inline logic for its own scope.

I have not revisited the direct field access because I was able to expand the use of offset-specialized methods. However, it might be interesting to see indy-based scope var access, which could specialize beyond the base set of offsets. I have no plans to do this right now.

@headius

This comment has been minimized.

Show comment
Hide comment
@headius

headius Sep 28, 2016

Member

Oh, this is also partially done on master too, but there's only two hand-written dynamic scope classes now: DynamicScope and ManyVarsDynamicScope.

Member

headius commented Sep 28, 2016

Oh, this is also partially done on master too, but there's only two hand-written dynamic scope classes now: DynamicScope and ManyVarsDynamicScope.

@enebo

enebo approved these changes Sep 29, 2016

Two trivial comments. My only reservation I think we will need to wait and see...we keep increasing our dependence of invokedynamic.

@headius headius merged commit 7d22b20 into jruby:master Sep 30, 2016

0 of 3 checks passed

continuous-integration/appveyor/pr AppVeyor build failed
Details
continuous-integration/appveyor/branch Waiting for AppVeyor build to complete
Details
continuous-integration/travis-ci/pr The Travis CI build is in progress
Details

@headius headius deleted the headius:generated_dynamicscopes branch Sep 30, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment