Tutorial
This page will explain how to use the @miniboxed
anntotation. Miniboxing is a source-to-source transformation that speeds up generic classes and methods when used with primitive value types, such as Unit
,Boolean
, Byte
, Char
, Short
, Int
, Float
, Double
or Long
.
Before you start using the plugin, please read this one page. It will explain how to use the @miniboxed
annotation, so your program will be optimized properly. Although we don’t recommend it, you can skip through to the example sbt project if you don’t plan to use miniboxing in your project right now.
Quick Facts
Here are a few quick facts about the @miniboxed
annotation:
- The miniboxing transformation is activated by annotating type parameters with
@miniboxed
, for both classes and methods:
- Miniboxing can speed up monomorphic code:
In this case, the miniboxing transformation will encounter an instantiation of C
with type argument Int
, and will rewrite the new
instance creation and the foo
method call to use direct value types, thus speeding up execution and avoiding boxing.
- Miniboxing can’t speed up generic code if the type parameters are not annotated with
@miniboxed
:
Since the type parameter T
of method bar_bad
is not marked as @miniboxed
, the erasure transformation will kick in instead of miniboxing. Although the C
class is transformed by miniboxing, erasure won’t perform any program rewriting to improve performance. Therefore, regardless of whether C
is optimized by miniboxing or not, the performance for bar_bad
will stay the same. This is intended, since miniboxed code needs to be compatible with erasure-generated code, at the cost of lower performance.
- To recover performance for the previous case, you can annotate the method parameter as
@miniboxed
:
Since the type parameter T
of bar_awesome
is marked as @miniboxed
, the method will have two versions: an optimized version for value types and the compatible, erasure-based, slow version of the method.
Optimized Trace
While reading the quick facts, you might have wondered whether you can explicitly instantiate the optimized variants of classes or call optimized methods created by miniboxing. Unfortunately the answer is no, you can’t, as this would compromise the type safety of the language. Therefore, when using the miniboxing plugin, you will have to pay special attention to making sure the hot methods in your code and the commonly used data structures are rewritten by the compiler to use the optimized variants. Luckily, this is not hard at all, you only need to remember three cases you may find yourself in.
But before we go on to the cases, we need to explain the concept of an optimized trace. Let’s imagine you’re writing a generic k-means algorithm, and the main iteration calls several methods. Ideally, you want the iteration itself and the methods it calls to be the miniboxing-optimized versions. Knowing the optimized methods have a special marking in their name, how could you check your algorithm is using them? One way to do this would be to throw an exception in one of the methods and check the last 2 stack frames – if the methods have the special miniboxed mark, they are the optimized ones. We call this an optimized trace.
Optimized traces are series of miniboxing-optimized methods that call each other. Let’s see an example stack trace, knowing _J
is one of the markings for miniboxing-optimized methods:
In the stack trace above, all our algrithm is miniboxing-optimized, since kmeans_J
, iter_J
and sum_J
all have the miniboxing prefix _J
, so our algorithm should run efficiently. It is not necessary to have all the program optimized, just the critical parts that execute for a long time. So your task, as a programmer, is to make sure the critical traces in your program only contain miniboxing-marked methods.
That being said, here are the three types of calls that make up the trace:
- optimized trace initiators, which start an optimized trace: method calls where the type argument is a value type and the type parameter is annotated with
@miniboxed
- optimized trace inhibitors, which end an optimized trace: calls to methods whose type parameter is not marked as
@miniboxed
- optimized trace propagators, which continue an optimized trace, but only if called from an optimized trace
An Example, Finally!
Knowing these three cases enables you to make sure the entire trace is optimized. Let’s take an example:
As before, the _J
suffix is an indication that the miniboxing-optimized version of the method was executed. We can say the optimized trace was initiated by using a @miniboxed
-annotated method with a value type.
Now, let’s test if we don’t initiate the optimized trace:
As expected, no more _J
s, so we’re using the erasure-based versions! That’s because String
is not a value type, so the optimized trace was not initiated at all. This shows that foo
, bar
and baz
are just propagators: if they are called as part of an optimized trace, they are optimized, but if the optimized trace is not initialized somewhere, the erasure-based slow versions are invoked.
Now, let’s see an inhibitor: let us remove the @miniboxed
annotation from bar
’s type parameter T
, in a second object, Test2
:
The result shouldn’t surprise you: the optimized trace was inhibited, since method bar
does not have an optimized version at all (e.g. the bytecode for object Test2
doesn’t contain bar_n_J
at all). But the thing to look out for is that erasure-based versions of the code will always call the erasure-based versions of other methods. This is why, although foo
has an optimized version, it won’t be called.
Classes as Data Structures
We will now focus more on classes. Classes encapsulate both data and code, but, in order to simplify things, we will first consider only data. As you probably figured out already, miniboxing transformed methods have two versions: a generic, erasure-compatible one, and an optimized, value type-carrying one. Pretty much the same happens to a class: it will be transformed into two classes, a generic, erasure-compatible one, and an optimized one:
A very rough approximation of the transformed code for class D
is:
The reason classes are duplicated is because they store data: since references and value types are incompatible on the Java Virtual Machine, we have to duplicate the class, such that the object layout is adapted for the type it is carrying.
The optimized version of the class is created by the instantiation: new D[U](...)
. The generic version, D_L
is used, except for two specific cases, when the instantation is rewritten to create the optimized version D_J
:
- if the type argument
U
is known and is a value type:new D[Int](3)
, which is rewritten tonew D_J[Int](3)
- if in the class or method containing type parameter
U
is marked with@miniboxed
, then the optimized version of that class or method will use the optimized version of the class:
This corresponds to some extent to the previous 3 cases:
- optimized trace initiators always create the optimized instance of the class
- optimized trace propagators create the optimized instance only in their optimized version (
E_L
createsD_L
,E_J
createsD_J
instances) - optimized trace inhibitors always create the generic instance of the class
Classes as Optimized Trace Initiators
Finally, we come to the last point: the code encapsulated in classes. We won’t go into details on how this is achieved, but calling a method on an optimized instance of a class (z_opt
in this case) behaves like an initiator, while calling a method on a generic instance of a class (z_gen
) behaves as an inhibitor:
Closing Remarks
For an alert reader, this section probably raised many more questions than it answered. The miniboxing encoding page explains the internals of miniboxing, and will answer all your questions, both on the theory and the implementation. Still, as a programmer using miniboxing, what you already know is enough to make sure your performance-critical code is optimized, and you can jump to the example project page.
Skipping one step ahead, the -P:minibox:log
flag passed to the Scala compiler will output all details on how classes are transformed and how their members are affected:
Comments
Comments are always welcome! But to make the best use of them, please consider this:
- If you have questions or feedback regarding the content of this page, please leave us a comment!
- If you have general questions about the miniboxing plugin, please ask on the mailing List.
- If you found a bug, please let us know on the github issue tracker.
Thanks! Looking forward to your messages!
comments powered by Disqus