new backend, part 2 of 2: initial (read: slow) support for MethodHandles

517 views
Skip to first unread message

Miguel Garcia

unread,
Mar 16, 2013, 4:29:21 PM3/16/13
to scala-i...@googlegroups.com

To recap, the new backend supports Java 6 and 7. In an experimental branch (based itself on the experimental backend) there's now support for MethodHandles, building upon "Late Closure Classes", ie the ability of the new backend to materialize anonymous closure classes as it emits bytecode.

And now the MethodHandles story. For backward compatibility the prototype assumes we want to still pass FunctionX objects, which now wrap a MethodHandle instance. That MH is invoked whenever apply() is called. For that to work, the values captured by the anon-closures should be available to the MH instance, via partial-application ("bindTo" in terms of JSR-292 API). Finally, as before, an anon-closure-class is instantiated each time an FunctionX object is passed; the difference is that before a dedicated custom class existed for each anon-closure, while now there's scala.runtime.MHAbsFun0 to 22 , and it's one of those that is instantiated. Each MHAbsFunX takes a single constructor argument (the MH in question).

Besides anon-closures, other candidates areas for MHs (not covered by the prototype) are (1) specialization; (2) updating typer to special-case MH.invoke and MH.invokeExact; among others.

Unlike for the new backend, documentation is lacking. The rest of this post discusses implementation aspects.

How to grab the branch with MethodHandles support:

    git clone git://github.com/magarciaEPFL/scala.git GenMHv1
    cd GenMHv1
    git checkout -b GenMHv1 origin/GenMHv1
    ant all.clean && ant



For perspective, the delta with respect to the non-MethodHandles backend is:

  https://github.com/magarciaEPFL/scala/compare/magarciaEPFL:GenRefactored5...magarciaEPFL:GenMHv1


So far results are a mixed bag:

The good:
  (a.1) not emitting anon-closure-classes shrinks (compiler + reflect) into 8 MB territory, previously 13 MB (and that's with "Lean Closure Classes", which already improves on the 16 MB that the current, "traditional", lowering of anon-closures produces).

The bad: No performance gain, in fact a performance loss (to be honest I wasn't expecting a perf gain on JDK7, however the perf degradation was larger than expected).

Ways to improve performance:

  (b.1) use JDK8 (actually any serious effort about MH should be on JDK8 anyway). But beware build.xml won't let you build on JDK8.

  (b.2) currently the newly added scala.runtime.MHAbsFunX (the counterpart to AbstractFunctionX) doesn't use MH.invokeExact , incurring heavy boxing overhead. That can be solved (more on this in a moment)

  (b.3) "partial application" ie binding of (closure-captured) arguments to a MethodHandle also involves boxing.

Each of the items above is amenable to a localized solution, however they aren't addressed in this version because experience has shown that compilation speed can be improved noticeably via old fashioned good engineering. Besides, we're not in a hurry to support JDK 8.

Coming back to emitting faster code for MethodHandles, four approaches that make sense (so much sense that probably they should have been followed from the start):

  (c.1) invoke instead of invokeWithArguments

For expediency the MH-based counterparts to AbstractFunctionX are emitted using the same tools as for AbstractFunctionX (ie genprod.scala and from there the compiler build does the rest). For example, the source of MHAbsFun2 is:

final class MHAbsFun2[
  @specialized(scala.Int, scala.Long, scala.Double) -T1,
  @specialized(scala.Int, scala.Long, scala.Double) -T2,
  @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R
](target: _root_.java.lang.invoke.MethodHandle)
extends AbstractFunction2[T1, T2, R] {
  def apply(v1: T1, v2: T2): R = {
    val args = new _root_.java.util.ArrayList[Any]()
    args.add(v1)
    args.add(v2)
    target.invokeWithArguments(args).asInstanceOf[R]
 }
}


A (way) more efficient version would use invokeExact (or plain invoke for that matter) specialized to the actual arg types, ie inside each of the following methods:

javap -classpath build/pack/lib/scala-library.jar scala.runtime.MHAbsFun2


Compiled from "AbstractFunction2.scala"
public class scala.runtime.MHAbsFun2<T1, T2, R> extends scala.runtime.AbstractFunction2<T1, T2, R> {

  public final java.lang.invoke.MethodHandle scala$runtime$MHAbsFun2$$target;

  public R apply(T1, T2);

  public boolean apply$mcZDD$sp(double, double);
  public double  apply$mcDDD$sp(double, double);
  public float   apply$mcFDD$sp(double, double);
  public int     apply$mcIDD$sp(double, double);
  public long    apply$mcJDD$sp(double, double);
  public void    apply$mcVDD$sp(double, double);

  public boolean apply$mcZDI$sp(double, int);
  public double  apply$mcDDI$sp(double, int);
  public float   apply$mcFDI$sp(double, int);
  . . .


(c.2) more efficient "partial application". Currently done by boxing each bound argument individually. For example the following code:

  def mA(captured: String) {
    (1 to 10) foreach { i => println(captured + i) }
  }


For the sake of discussion, let's forget for a moment about -neo:o2 (which, er, would optimize away the closure above, and deliver a while loop).

The anon-closure passed to Range.foreach() captures a String value, "captured". The bytecode below shows the MH instance passed as argument to the constructor of MHAbsFun1, after binding "captured" via MHUtil.bindAt() where boxing occurs. Binding without boxing would already be an improvement, as well as binding values in block.

        15: new           #47                 // class scala/runtime/MHAbsFun1
        18: dup                              
        19: aload_1                           // load "captured" value
                                              // a method-pointer for the "closure body" is loaded onto the operand stack,
                                              // the method holding that body goes by the name of dlgt$27b8b21$1
        20: ldc           #52                 // MethodHandle invokevirtual Test$.dlgt$27b8b21$1:(ILjava/lang/String;)V
        22: ldc           #53                 // int 2
        24: invokestatic  #59                 // Method scala/runtime/MHUtil.bindAt:(Ljava/lang/Object;Ljava/lang/invoke/MethodHandle;I)Ljava/lang/invoke/MethodHandle;
        27: getstatic     #61                 // Field MODULE$:LTest$;
        30: invokevirtual #67                 // Method java/lang/invoke/MethodHandle.bindTo:(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
        33: invokespecial #70                 // Method scala/runtime/MHAbsFun1."<init>":(Ljava/lang/invoke/MethodHandle;)V
        36: invokevirtual #74                 // Method scala/collection/immutable/Range.foreach$mVc$sp:(Lscala/Function1;)V


(c.3) "invokedynamic" At the end of the day, *some code* has to run behind the curtain of "invokedynamic", code that should make things faster. If you're suggesting using invokedynamic, it would be great to know what that code should look like.

(c.4) currently LDC MH-constant binds invokevirtual-style (for a non-static LCC endpoint) but it could bind invokespecial-style.


That's the current status, comments are welcome!



Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/

ijuma

unread,
Mar 18, 2013, 4:35:24 AM3/18/13
to scala-i...@googlegroups.com
Great to see the work being done here.

On Saturday, 16 March 2013 20:29:21 UTC, Miguel Garcia wrote:
The bad: No performance gain, in fact a performance loss (to be honest I wasn't expecting a perf gain on JDK7, however the perf degradation was larger than expected).

Are these numbers available somewhere? And what JDK 7 version was used in the test? The next JDK 7 update will include a new version of HotSpot with a lot of changes in this area.

Best,
Ismael

Miguel Garcia

unread,
Mar 18, 2013, 5:26:00 AM3/18/13
to scala-i...@googlegroups.com

The benchmark was compiling the compiler, ie support for MethodHandles is good enough to bootstrap. Out of the 10 or so tests that failed, half were about trying to serialize a MethodHandle-carrying anon-closure. I didn't look in detail about the other failings tests, because performance was bad enough to quickly convince me it just makes sense to wait for better VM support for MH on JDK7.

I'm using:
java version "1.7.0_15"
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)

Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/

√iktor Ҡlang

unread,
Mar 18, 2013, 5:34:35 AM3/18/13
to scala-i...@googlegroups.com


--
You received this message because you are subscribed to the Google Groups "scala-internals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-interna...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Viktor Klang
Director of Engineering

Twitter: @viktorklang

Simon Ochsenreither

unread,
Mar 18, 2013, 6:03:30 AM3/18/13
to scala-i...@googlegroups.com

Just be prepared that tasksupport related tests might hang on Java 8, due to Properties.isJavaAtLeast.

√iktor Ҡlang

unread,
Mar 18, 2013, 7:12:47 AM3/18/13
to scala-i...@googlegroups.com
:/


On Mon, Mar 18, 2013 at 11:03 AM, Simon Ochsenreither <simon.och...@gmail.com> wrote:

Just be prepared that tasksupport related tests might hang on Java 8, due to Properties.isJavaAtLeast.

--
You received this message because you are subscribed to the Google Groups "scala-internals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-interna...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Simon Ochsenreither

unread,
Mar 18, 2013, 9:30:58 AM3/18/13
to scala-i...@googlegroups.com
https://issues.scala-lang.org/browse/SI-7265

What's the suggested approach for targeting 2.9.x, 2.10.x and trunk? Will a PR against 2.9.x suffice or do I need to create one against 2.9.x and one against 2.10/trunk?

Rich Oliver

unread,
Mar 18, 2013, 9:59:24 AM3/18/13
to scala-i...@googlegroups.com
On Monday, March 18, 2013 9:26:00 AM UTC, Miguel Garcia wrote:

The benchmark was compiling the compiler, ie support for MethodHandles is good enough to bootstrap. Out of the 10 or so tests that failed, half were about trying to serialize a MethodHandle-carrying anon-closure. I didn't look in detail about the other failings tests, because performance was bad enough to quickly convince me it just makes sense to wait for better VM support for MH on JDK7.
Sorry just to clarify, I presume you mean wait for JDK8.

Ismael Juma

unread,
Mar 18, 2013, 10:11:54 AM3/18/13
to scala-i...@googlegroups.com
Not necessarily as JDK 7 has been getting a new HotSpot version periodically. Having said that, it is unclear whether this will continue given the potentially disrupting changes in the latest HotSpot (for PermGen removal). We will see, I guess.

Best,
Ismael

Miguel Garcia

unread,
Mar 18, 2013, 10:14:11 AM3/18/13
to scala-i...@googlegroups.com
Regarding further benchmarking of MHs, a plan coud be:

  (1) remove the sources of inefficiency (like, MH.invoke vs MH.invokeWithArguments) mentioned in
      https://groups.google.com/d/msg/scala-internals/uBxprJixpwk/jG78X1k_92IJ

      That improves perf on both JDK7 and 8, thus should be tried first.

  (2) update benchmarks as new versions of JDK7 and 8 VMs become available


The tasks above avoid committing to JDK8, or to HotSpot for that matter.

The prototype uses JSR-292 APIs only. I guess there must be HotSpot-specific APIs or perhaps JDk8-specific APIs for better MH performance, but even if I had heard about them :-) I guess I would have stayed clear from that all the same.


Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/

James Iry

unread,
Mar 18, 2013, 10:16:51 AM3/18/13
to scala-i...@googlegroups.com
I think he actually meant the JVM implementations of JDK 7.

Scott Carey

unread,
Sep 19, 2013, 12:35:16 AM9/19/13
to scala-i...@googlegroups.com
Java 7 u40 is out now, and has some significant performance improvements to method handles.   I wonder what the performance is like now.

Chee Seng Chua

unread,
Feb 19, 2014, 4:03:31 AM2/19/14
to scala-i...@googlegroups.com
Hi,

I can't get o1-o3 option to work following the steps described here:

http://magarciaepfl.github.io/scala/

the error says:

[error] bad option: '-Ybackend:o1'
[error] (scalatest/compile:compile) Compilation failed
[error] Total time: 1 s, completed Feb 19, 2014 4:59:10 PM

is the instructions out-dated?

Miguel Garcia

unread,
Feb 19, 2014, 4:16:50 AM2/19/14
to scala-i...@googlegroups.com

Hi,

Optimization levels o1 to o3 will work provided branch GenRefactored99sX  has been checked out (or whathever the "latest" branch at the time is, http://magarciaepfl.github.io/scala/ )

The new optimizer (BCodeOpt) lives only on that branch, it's not part of the scalac distribution.

Miguel

Chee Seng Chua

unread,
Feb 19, 2014, 7:02:02 AM2/19/14
to scala-i...@googlegroups.com
Hi,

Thanks for the response!  I followed the post and did this:

git clone git://github.com/magarciaEPFL/scala.git GenRefactored99sX
cd GenRefactored99sX
git checkout -b GenRefactored99sX origin/GenRefactored99sX
ant all.clean && ant

with the branch built successfully, I changed my sbt using scalaVersion and scalaHome, and add in scalacOptions, but I get the above error. Perhaps I am missing anything?

Miguel Garcia

unread,
Feb 19, 2014, 7:09:16 AM2/19/14
to scala-i...@googlegroups.com


I hope the following from the Getting Started is enough to get sbt to use the new optimizer:

scalaVersion := "2.11.0-SNAPSHOT",

scalaHome := Some(file("scala-with-new-backend/build/pack")),

scalacOptions ++= Seq( . . . your options
                      "-Ybackend:o3", // the highest level of the new optimizer
                       . . . more of your options
                     ),

If you're getting an error message please post a GitHub gist with it.


Miguel



On Wednesday, February 19, 2014 1:02:02 PM UTC+1, Chee Seng Chua wrote:
Hi,

Chee Seng Chua

unread,
Feb 19, 2014, 7:39:11 AM2/19/14
to scala-i...@googlegroups.com
Hi Miguel,

I am trying to use the new backend for building scalatest, I check in what I have in the following branch:

https://github.com/cheeseng/scalatest/tree/new-backend

the sbt build file is:

https://github.com/cheeseng/scalatest/blob/new-backend/project/scalatest.scala

when I tried compile it, I got:

[error] bad option: '-Ybackend:o3'

[error] (scalatest/compile:compile) Compilation failed

Can you help me to see what's missing?

Thanks!

Chee Seng
Reply all
Reply to author
Forward
0 new messages