More indy perf anecdotes

Rémi Forax forax at univ-mlv.fr
Sun Apr 10 16:15:06 PDT 2011


Hi Charles !

On 04/11/2011 12:18 AM, Charles Oliver Nutter wrote:
> Today I played a bit more with expanding our use of invokedynamic in
> JRuby. The experiment today was to replace direct guarded calls with
> indy calls.
>
> Specifically, when calling an operator (+, -,<, etc) with a literal
> integer, we use a different call site that checks instanceof
> RubyFixnum and calls the actual method (op_plus, op_minus, op_lt,
> etc). So there's a monomorphic path as long as the target is also a
> RubyFixnum. code =>  PlusCallSite (for example) =>  RubyFixnum.op_plus.
>
> Replacing this with indy uses a guardWithTest where the test is "self
> instanceof RubyFixnum", and the target path binds directly through to
> the RubyFixnum method with a bit of argument dropping.
>
> This is a good test for indy perf versus the same logic done with
> instance, casts, and invokevirtual.
>
> With the previous logic, fib(35) settles in around 0.74s. With the
> indy logic, performance decreases to 0.83s or so.
>
> This does beat logic that dispatched operators with literal values
> like any other, passing a RubyFixnum object and doing a full type
> identity and modification guard...that comes in closer to 0.95 or
> worse. Fixnum math operations really beg for as direct logic as
> possible, and it looks like indy adds a healthy amount of overhead
> over all virtual invocations.
>
> I have not done any investigation into the resulting assembly yet.

I think you can fix a little bit your implementation:
First instead of sending the name of the operation at each call
you should use the indy name to encode that name
instead of pushing it on the stack.
So fixnumFallback should bind the callsite and the indy name.

You should precalculate all your MethodHandle get using
a find(). As far as I know the VM will create a new MH each time.
In PHP.reboot, I store them in different classes (one by operation)
to avoid to initialize too much static variables at start-up.

When you find a MH (in a fast case or in slow case),
you should call it instead of relying on self.callMethod().

Also an interesting thing to test is that in that case you know
the value of the constant (which is a primitive) so instead
of pushing it as argument, you should try to send it as parameter
of the BSM and bind it to your method handle.
I wonder if it's more efficient ?

Rémi


> Here's the original logic for +, in PlusCallSite (bytecode emitted
> calls virtual method CallSite.call):
>
>     public IRubyObject call(ThreadContext context, IRubyObject caller,
> IRubyObject self, long fixnum) {
>          if (self instanceof RubyFixnum) {
>              return ((RubyFixnum) self).op_plus(context, fixnum);
>          } else if (self instanceof RubyFloat) {
>              return ((RubyFloat) self).op_plus(context, fixnum);
>          }
>          return super.call(context, caller, self, fixnum);
>      }
>
> Here's how the indy logic does it:
>
> ASM emission:
>      public void invokeBinaryFixnumRHS(String name, CompilerCallback
> receiverCallback, long fixnum) {
>          if (receiverCallback != null) {
>              receiverCallback.call(methodCompiler);
>          } else {
>              methodCompiler.loadSelf();
>          }
>          methodCompiler.loadThreadContext();
>          method.ldc(name);
>          method.ldc(fixnum);
>
>          String signature = sig(IRubyObject.class,
> params(IRubyObject.class, ThreadContext.class, String.class,
> long.class));
>
>          method.invokedynamic("fixnumOperator", signature,
> InvokeDynamicSupport.bootstrapHandle("bootstrapOperatorFixnum"));
>      }
>
> Bootstrap:
>      public static CallSite
> bootstrapOperatorFixnum(MethodHandles.Lookup lookup, String name,
> MethodType type) throws NoSuchMethodException, IllegalAccessException
> {
>          CallSite site = new MutableCallSite(type);
>
>          MethodType fallbackType = type.insertParameterTypes(0,
> MutableCallSite.class);
>          MethodHandle myFallback = MethodHandles.insertArguments(
>                  lookup.findStatic(InvokeDynamicSupport.class, "fixnumFallback",
>                  fallbackType),
>                  0,
>                  site);
>          site.setTarget(myFallback);
>          return site;
>      }
>
> Fallback, test, and slow-path (when a non-Fixnum is encountered) logic:
>      public static IRubyObject fixnumFallback(MutableCallSite site,
> IRubyObject self, ThreadContext context, String name, long value)
> throws Throwable {
>          RubyFixnum fixnum = context.runtime.newFixnum(value);
>          if (self instanceof RubyFixnum) {
>              String realMethod = MethodIndex.getFastOpsMethod(name);
>              if (realMethod != null) {
>                  try {
>                      MethodHandle direct =
> MethodHandles.lookup().findVirtual(RubyFixnum.class, realMethod,
> MethodType.methodType(IRubyObject.class, ThreadContext.class,
> long.class));
>                      MethodHandle target =
> MethodHandles.explicitCastArguments(direct,
> MethodType.methodType(IRubyObject.class, IRubyObject.class,
> ThreadContext.class, long.class));
>                      target = MethodHandles.dropArguments(target, 2,
> String.class);
>
>                      MethodHandle test =
> MethodHandles.lookup().findStatic(InvokeDynamicSupport.class,
> "fixnumTest", site.type().changeReturnType(boolean.class));
>
>                      MethodType fallbackType =
> site.type().insertParameterTypes(0, MutableCallSite.class);
>                      MethodHandle fallback = MethodHandles.insertArguments(
>
> MethodHandles.lookup().findStatic(InvokeDynamicSupport.class,
> "fixnumFallback",
>                              fallbackType),
>                              0,
>                              site);
>
>                      site.setTarget(MethodHandles.guardWithTest(test,
> target, fallback));
>                      return self.callMethod(context, name, fixnum);
>                  } catch (NoSuchMethodException nsme) {
>                      // just fall through
>                  }
>              }
>          } else {
>              MethodType slowType = site.type().insertParameterTypes(0,
> MutableCallSite.class);
>              MethodHandle target = MethodHandles.insertArguments(
>
> MethodHandles.lookup().findStatic(InvokeDynamicSupport.class,
> "fixnumSlow",
>                      slowType),
>                      0,
>                      fixnum);
>              site.setTarget(target);
>          }
>
>          return self.callMethod(context, name, fixnum);
>      }
>
>      public static boolean fixnumTest(IRubyObject self, ThreadContext
> context, String name, long value) {
>          return self instanceof RubyFixnum;
>      }
>
>      public static IRubyObject fixnumSlow(IRubyObject fixValue,
> IRubyObject self, ThreadContext context, String name, long value)
> throws Throwable {
>          return self.callMethod(context, name, fixValue);
>      }
>
> - Charlie
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.java.net
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev



More information about the mlvm-dev mailing list