Tuesday 12 November 2013

Scala Specialization: a primer on the translation scheme

Scala's specialization facility is present since 2.8 and is enabled selectively with the @specialized annotation that can annotate generic parameters. This means that the type parameter can be specialized on the specified primitive types to avoid the performance burden of boxing and unboxing. In an effort to understand the basic idea of the translation scheme I created this post for a quick reference of mine.

In a nutshell


The core concept of the translation scheme is that normal method invocations to instances must always work when the instance is not specialized and on the other hand if enough type information exists the compiler will rewrite method calls to the specialized variants.

In the example below, the original code defines a Specialized class, that indicates two specializations, one to Int and the other to Double. This annotated definition creates three generated classes: a generic one that is called Specialized (with regular type erasure, where generic type arguments are erased and substituted by bounds) and two specializations Specialized$mcI$sp and Specialized$mcD$sp. These latter extend Specialized, overriding methods with specialized ones. The interesting part is that each specialized variant overrides the apply method that returns Object and a code generated one. The apply method in each class delegates the call to the generated method that performs an operation on the primitive type itself. Additionally, the generic versions of the methods of the specialized classes (like apply in line 40) are preserved and also specialized versions of methods on the generic class are also preserved like in lines 4,5. This duality in the wiring process ensures both correctness (e.g., calling the generic method apply in the specialized instance) and ensures proper late binding.

A question that was answered to me in this comment by Aleksandar Prokopec was: Why in line 22, the ret.apply() call is rewritten to ret.apply$mcI$sp();? The compiler acts pro-actively here by rewriting every apply call to the specialized one. The static type of the receiver directs the compiler to rewrite the call, but then the proper version of the method body is found via late binding on the actual instance that was passed (and potentially calling a specialized method to avoid boxing).

References


A more elaborate description of the initial design, implementation and semantics of Scala Specialization is included in Iulian DragoČ™ PhD thesis which is about Compiling Scala for Performance. Also, in Aleksandar Prokopec's blog there is an elaborate post about Quirks of Scala Specialization outlining several guidelines. Finally, I don't know what the current state of specialization is but back in the 2012, the rethinking specialization spawned the SIP: Changes to the Specialization Phase.

No comments:

Post a Comment