Remember all those little classfiles for anonymous closures?
How about using the following as template instead? (in the example, for lambdas of arity 2).
package scala.runtime
final class ReflBasedFunR2[-T1, -T2, +R](
delegate: java.lang.reflect.Method,
receiver: Object,
args: Array[Object]
) extends AbstractFunction2[T1, T2, R] {
/** Apply the body of this function to the argument{s}.
* @return the result of function application.
*/
def apply(v1: T1, v2: T2): R = {
args(0) = v1.asInstanceOf[AnyRef]
args(1) = v2.asInstanceOf[AnyRef]
try {
delegate.invoke(receiver, args: _*).asInstanceOf[R]
} catch {
case ite: java.lang.reflect.InvocationTargetException => throw ite.getCause()
}
}
}
In exchange for the 30% code reduction, each apply() now involves a reflective invocation. Isn't that a lot slower? On Android I haven't tried, but when compiling the compiler the slowdown lies between 5% and 10%. Two measures were taken to lessen the performance impact:
- the "target method" is looked up at most once, and cached afterwards. Lookup via getDeclaredMethod() is the expensive part of "reflection", successive invocations on it are comparatively unexpensive.
- in that particular benchmark, the compiler was compiled with the new optimizer, at optimization level -neo:o3 (ie, cross-library method and closure inlining). Doing so results in a program that's both faster and smaller.
Given
ReflBasedFun... subclass
AbstractFunctionX , the receiver won't notice the difference. The "target method" is public thus posing no access restriction when called from the lambda. On the other hand, the "producer" of a lambda has to know how to obtain it, and the anon-closure-class isn't emitted anymore. In that sense, binary compatibility is broken.
To benchmark, compile with -closurify:reflect using the new compiler backend:
http://magarciaepfl.github.io/scala/ (Getting Started, instructions for sbt, etc., can be found there).
How do reflect-based lambdas compare with MethodHandles? To recap, support for MethodHandles was removed (for now) from the new backend because performance was disappointing. As of JDK 7, both reflection and method-handles result in similarly small JARs, but reflection-based lambdas (currently) outerperform their MethodHandle-based counterparts. And there's also Android, right?
Under the hood, reflection-based lambdas build upon the Late-Closure-Classes approach, in just two commits:
(1) adding scala.runtime.ReflBasedFun...
https://github.com/magarciaEPFL/scala/commit/67b1f0936c19cfd42d2192a87fccdf285fe6daab (2) rewriting for reflect-based lambdas (including ... documentation!)
https://github.com/magarciaEPFL/scala/commit/8ed60361845f376c99328c3f81cd47678a57e8e4#L5R34How about testing? The compiler bootstraps using reflection-based lambdas. Therefore -closurify:reflect must be doing something well.
Ah, but there are areas for improvement. Some tests simply have been tuned to "one little classfile per lambda", for example the following fails under -closurify:reflect
% diff test/files/specialized/fft-specialized.log test/files/specialized/fft.check
@@ -1,4 +1,4 @@
Processing 65536 items
Boxed doubles: 0
-Boxed ints: 3
+Boxed ints: 2
Boxed longs: 1179811A few more tests fail because of serialization complaining about j.l.r.Method standing in the way (the `delegate` field in
ReflBasedFun... should have been transient, after all). Here's the list of failing tests, easy to reproduce. Want to help with those?
SCALAC_OPTS=-closurify:reflect ./test/partest path/to/test
[partest] !! 1 - continuations-run/ifelse2.scala [non-zero exit code]
[partest] !! 227 - run/t6467.scala [non-zero exit code]
[partest] !! 552 - run/t4895.scala [non-zero exit code]
[partest] !! 662 - run/t6052.scala [non-zero exit code]
[partest] !! 902 - run/t6410.scala [output differs]
[partest] !! 931 - run/t5018.scala [non-zero exit code]
[partest] !! 78 - jvm/t1143.scala [non-zero exit code]
[partest] !! 86 - jvm/serialization-new.scala [non-zero exit code]
[partest] !! 100 - jvm/serialization.scala [non-zero exit code]
[partest] !! 101 - jvm/t1116.scala [non-zero exit code]
[partest] !! 113 - jvm/t1143-2 [non-zero exit code]
[partest] !! 18 - scalacheck/range.scala [ScalaCheck test failed]
[partest] !! 21 - scalacheck/parallel-collections [ScalaCheck test failed]
[partest] !! 22 - scalacheck/Ctrie.scala [ScalaCheck test failed]
[partest] !! 15 - specialized/fft.scala [output differs]
[partest] !! 20 - specialized/spec-absfun.scala [output differs]Miguel
http://lampwww.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/