Scala's Privates
August 5, 2017
As well as the usual public
, protected
, and private
access control modifiers, which have with same rules as their Java siblings, Scala also supports the private[this]
modifier, which prevents access to a property from other instances. For example, the following code would not compile:
Whereas this would be fine:
Whilst the access control rules are applied at compile time, they lead to slightly different code generation. In the case of a property marked private
a private field, a getter method, and a setter method are added to the class. All code that accesses the field (aside from its initialisation) will go through either the getter or setter. On the other hand, a private[this]
field will only add the field to the class, and all access to the field is direct, rather than through accessor methods (as, after all, all access is only done by the instance).
The behaviour of the Scala compiler can be verified using the javap
tool, part of the JDK that prints Java bytecode in a human readable form. I compiled the following two Scala classes:
class A { private var x: Int = 37; def add() = x = x + 1 }
class B { private[this] var x: Int = 37; def add() = x = x + 1 }
The output of javap -p -c A
, the class that doesn’t use private[this]
, is:
Compiled from "Classes.scala"
public class A {
private int x;
private int x();
Code:
0: aload_0
1: getfield #13 // Field x:I
4: ireturn
private void x_$eq(int);
Code:
0: aload_0
1: iload_1
2: putfield #13 // Field x:I
5: return
public void add();
Code:
0: aload_0
1: aload_0
2: invokespecial #22 // Method x:()I
5: iconst_1
6: iadd
7: invokespecial #24 // Method x_$eq:(I)V
10: return
public A();
Code:
0: aload_0
1: invokespecial #27 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 37
7: putfield #13 // Field x:I
10: return
}
The particulars of the JVM’s instructions aren’t important for our discussion, but we can see that Scala methods x()
and x_=(int)
are generated here, and we can see that they are used in the add
method. Meanwhile, we see the following for javap -c -p B
, the class using private[this]
, is:
Compiled from "Classes.scala"
public class B {
private int x;
public void add();
Code:
0: aload_0
1: aload_0
2: getfield #14 // Field x:I
5: iconst_1
6: iadd
7: putfield #14 // Field x:I
10: return
public B();
Code:
0: aload_0
1: invokespecial #19 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 37
7: putfield #14 // Field x:I
10: return
}
Here the add
method directly accesses the field via the getfield
and putfield
instructions, rather than generating accessor methods. The astute reader may wonder whether Scala’s use of accessor methods for all private properties is slower than using private[this]
or writing similar code in Java. Should you aim to use private[this]
whenever you can to give yourself a performance boost?
The short answer is no. The JVM will eventually inline the getter and setter where they are used so that each access is executing the same native code as the private[this]
version. In my tests, over 10 million invocations of the add
method the class that used private
was around 1% slower than the one using private[this]
, but over 100 million invocations it was around 0.3% slower.
By passing the flags -J-XX:+PrintCompilation -J-XX:+UnlockDiagnosticVMOptions -J-XX:+PrintInlining
to the scala
command you can view when a method is inlined by the JVM (these are Java flags, hence the need to prepend them with -J
). I found on my machine that it generally took fewer than 20,000 invocations of the add
method for the x_=
method to get inlined inside it, leading to identical execution of both versions. The number of invocations of the method isn’t a fantastic metric to go by, as other factors (CPU load, timing, etc) also play a part in when a method gets inlined, and not all methods can be inlined because they are too large. For more complex methods, the cost of method invocation is much smaller than actually executing the method.
private[this]
should therefore be used when you need it for the specific access control for which it was designed. There is no need to hand optimise your code to use private[this]
, the JVM will do it for you!