Scala fait de plus en plus d'efforts pour réduire le coût de l'abstraction.
Dans les commentaires en ligne dans le code, j'explique les caractéristiques de performance de l'accès aux tableaux, des types pimpés, des types structurels et de l'abstraction sur les primitives et les objets.
Tableaux
object test {
/**
* From the perspective of the Scala Language, there isn't a distinction between
* objects, primitives, and arrays. They are all unified under a single type system,
* with Any as the top type.
*
* Array access, from a language perspective, looks like a.apply(0), or a.update(0, 1)
* But this is compiled to efficient bytecode without method calls.
*/
def accessPrimitiveArray {
val a = Array.fill[Int](2, 2)(1)
a(0)(1) = a(1)(0)
}
// 0: getstatic #62; //Field scala/Array$.MODULE$:Lscala/Array$;
// 3: iconst_2
// 4: iconst_2
// 5: new #64; //class test$$anonfun$1
// 8: dup
// 9: invokespecial #65; //Method test$$anonfun$1."<init>":()V
// 12: getstatic #70; //Field scala/reflect/Manifest$.MODULE$:Lscala/reflect/Manifest$;
// 15: invokevirtual #74; //Method scala/reflect/Manifest$.Int:()Lscala/reflect/AnyValManifest;
// 18: invokevirtual #78; //Method scala/Array$.fill:(IILscala/Function0;Lscala/reflect/ClassManifest;)[Ljava/lang/Object;
// 21: checkcast #80; //class "[[I"
// 24: astore_1
// 25: aload_1
// 26: iconst_0
// 27: aaload
// 28: iconst_1
// 29: aload_1
// 30: iconst_1
// 31: aaload
// 32: iconst_0
// 33: iaload
// 34: iastore
// 35: return
Pimp My Library
/**
* Rather than dynamically adding methods to a meta-class, Scala
* allows values to be implicity converted. The conversion is
* fixed at compilation time. At runtime, there is an overhead to
* instantiate RichAny before foo is called. HotSpot may be able to
* eliminate this overhead, and future versions of Scala may do so
* in the compiler.
*/
def callPimpedMethod {
class RichAny(a: Any) {
def foo = 0
}
implicit def ToRichAny(a: Any) = new RichAny(a)
new {}.foo
}
// 0: aload_0
// 1: new #85; //class test$$anon$1
// 4: dup
// 5: invokespecial #86; //Method test$$anon$1."<init>":()V
// 8: invokespecial #90; //Method ToRichAny$1:(Ljava/lang/Object;)Ltest$RichAny$1;
// 11: invokevirtual #96; //Method test$RichAny$1.foo:()I
// 14: pop
// 15: return
Types structurels (ou typage canard)
/**
* Scala allows 'Structural Types', which let you have a compiler-checked version
* of 'Duck Typing'. In Scala 2.7, the invocation of .size was done with reflection.
* In 2.8, the Method object is looked up on first invocation, and cached for later
* invocations..
*/
def duckType {
val al = new java.util.ArrayList[AnyRef]
(al: { def size(): Int }).size()
}
// [snip]
// 13: invokevirtual #106; //Method java/lang/Object.getClass:()Ljava/lang/Class;
// 16: invokestatic #108; //Method reflMethod$Method1:(Ljava/lang/Class;)Ljava/lang/reflect/Method;
// 19: aload_2
// 20: iconst_0
// 21: anewarray #102; //class java/lang/Object
// 24: invokevirtual #114; //Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
// 27: astore_3
// 28: aload_3
// 29: checkcast #116; //class java/lang/Integer
Spécialisation
/**
* Scala 2.8 introduces annotation driven specialization of methods and classes. This avoids
* boxing of primitives, at the cost of increased code size. It is planned to specialize some classes
* in the standard library, notable Function1.
*
* The type parameter T in echoSpecialized is annotated to instruct the compiler to generated a specialized version
* for T = Int.
*/
def callEcho {
echo(1)
echoSpecialized(1)
}
// public void callEcho();
// Code:
// Stack=2, Locals=1, Args_size=1
// 0: aload_0
// 1: iconst_1
// 2: invokestatic #134; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
// 5: invokevirtual #138; //Method echo:(Ljava/lang/Object;)Ljava/lang/Object;
// 8: pop
// 9: aload_0
// 10: iconst_1
// 11: invokevirtual #142; //Method echoSpecialized$mIc$sp:(I)I
// 14: pop
// 15: return
def echo[T](t: T): T = t
def echoSpecialized[@specialized("Int") T](t: T): T = t
}
Clôtures et compréhensions For
En Scala for
se traduit par une chaîne d'appels à des fonctions d'ordre supérieur : foreach
, map
, flatMap
y withFilter
. C'est très puissant, mais vous devez être conscient que le code suivant n'est pas aussi efficace qu'une construction similaire en Java. Scala 2.8 @spécialisera Function1 pour au moins Double
y Int
et, espérons-le, @specialize Traversable#foreach
ce qui permettra au moins de supprimer le coût de la mise en boîte.
Le corps de la for-compréhension est transmis sous la forme d'une fermeture, qui est compilée dans une classe interne anonyme.
def simpleForLoop {
var x = 0
for (i <- 0 until 10) x + i
}
// public final int apply(int);
// 0: aload_0
// 1: getfield #18; //Field x$1:Lscala/runtime/IntRef;
// 4: getfield #24; //Field scala/runtime/IntRef.elem:I
// 7: iload_1
// 8: iadd
// 9: ireturn
// public final java.lang.Object apply(java.lang.Object);
// 0: aload_0
// 1: aload_1
// 2: invokestatic #35; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
// 5: invokevirtual #37; //Method apply:(I)I
// 8: invokestatic #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
// 11: areturn
// public test$$anonfun$simpleForLoop$1(scala.runtime.IntRef);
// 0: aload_0
// 1: aload_1
// 2: putfield #18; //Field x$1:Lscala/runtime/IntRef;
// 5: aload_0
// 6: invokespecial #49; //Method scala/runtime/AbstractFunction1."<init>":()V
// 9: return
L ligne 4 : 0
// 0: new #16; //class scala/runtime/IntRef
// 3: dup
// 4: iconst_0
// 5: invokespecial #20; //Method scala/runtime/IntRef."<init>":(I)V
// 8: astore_1
// 9: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
// 12: iconst_0
// 13: invokevirtual #29; //Method scala/Predef$.intWrapper:(I)Lscala/runtime/RichInt;
// 16: ldc #30; //int 10
// 18: invokevirtual #36; //Method scala/runtime/RichInt.until:(I)Lscala/collection/immutable/Range$ByOne;
// 21: new #38; //class test$$anonfun$simpleForLoop$1
// 24: dup
// 25: aload_1
// 26: invokespecial #41; //Method test$$anonfun$simpleForLoop$1."<init>":(Lscala/runtime/IntRef;)V
// 29: invokeinterface #47, 2; //InterfaceMethod scala/collection/immutable/Range$ByOne.foreach:(Lscala/Function1;)V
// 34: return