Scala is doing an increasing good job of reducing the cost of abstraction.
In the comments inline in the code, I explain the performance characteristics of Array access, pimped types, Structural Types, and abstracting over primitives and objects.
Arrays
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
Structural Types (aka Duck Typing)
/**
* 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
Specialisation
/**
* 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
}
Closures and For Comprehensions
In Scala for
is translated to a chain of calls to higher order functions: foreach
, map
, flatMap
and withFilter
. This is really powerful, but you need to be aware that the following code isn't nearly as efficient as a similar looking construct in Java. Scala 2.8 will @specialize Function1 for at least Double
and Int
, and hopefully will also @specialize Traversable#foreach
, which will at least remove the boxing cost.
The body of the for-comprehension is passed as a closure, which is compiled to an anonymous inner class.
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
LineNumberTable:
line 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