Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why shouldn't one make every Scala instance variable a lazily initialized one?

Tags:

scala

Except from the added verbosity are there any other strong reasons why one shouldn't state that every instance variable should be lazily initialized ?

like image 845
Radu Stoenescu Avatar asked Jun 26 '13 18:06

Radu Stoenescu


People also ask

What is lazy initialization in Scala?

Lazy initialization means that whenever an object creation seems expensive, the lazy keyword can be stick before val. This gives it the advantage to get initialized in the first use i.e. the expression inbound is not evaluated immediately but once on the first access. Example: // Scala program of Lazy val.

What is the use of lazy Val in Scala?

Scala provides a nice language feature called lazy val that defers the initialization of a variable. The lazy initialization pattern is common in Java programs. Though it seems tempting, the concrete implementation of lazy val has some subtle issues.

What does a lazy Val do?

7 Answers. Show activity on this post. The difference between them is, that a val is executed when it is defined whereas a lazy val is executed when it is accessed the first time. In contrast to a method (defined with def ) a lazy val is executed once and then never again.


1 Answers

First of all: if something goes wrong in the initalization of a lazy val (like accessing an external resource that does not exist), you will only notice it the first time you access the val, whereas with a normal val you will notice as soon as the object is being constructed. You can also have cyclic dependencies in lazy vals which will lead to the class not working at all (one of the dreaded NullPointerExceptions), but you will only find out the first time you access one of the connected lazy vals.

So lazy vals make the program less deterministic, which is always a bad thing.

Second: There is a runtime overhead involved with a lazy val. A lazy val is currently implemented by a private bitmask (int) in a class using lazy vals (one bit for each lazy val, so if you have more than 32 lazy vals there will be two bitmasks etc.)

To make sure that the lazy val initializer will only be run exactly once, there is a synchronized write to the bitmask when the field is initialized and a volatile read every time the field is accessed. Now a volatile read is pretty cheap on the x86 architecture, but a volatile write can be really expensive.

As far as I know there is an effort underway to optimize this in a future version of scala, but there will always be an overhead to check if the field is initialized compared to a straight val access. For example the extra code for lazy val access might prevent a method from being inlined.

Of course for a very small class the memory overhead of the bitmask might also be relevant.

But even if you don't have any performance problems, it is good to figure out the order in which vals depend on each other and just sort them in that order and use normal vals.

Edit: here is a code example that illustrates the nondeterminism you might get if you use lazy vals:

class Test {
  lazy val x:Int = y
  lazy val y:Int = x
}

You can create an instance of this class without any problems, but as soon as you access either x or y you will get a StackOverflow. This is of course an artificial example. In the real world you have much longer and non-obvious dependency cycles.

Here is a scala console session using :javap that illustrates the runtime overhead of a lazy val. First a normal val:

scala> class Test { val x = 0 }
defined class Test

scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public int x();
  Code:
   0:   aload_0
   1:   getfield    #11; //Field x:I
   4:   ireturn

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #17; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   iconst_0
   6:   putfield    #11; //Field x:I
   9:   return

}

And now the lazy val:

scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public volatile int bitmap$0;

public int x();
  Code:
   0:   aload_0
   1:   getfield    #12; //Field bitmap$0:I
   4:   iconst_1
   5:   iand
   6:   iconst_0
   7:   if_icmpne   45
   10:  aload_0
   11:  dup
   12:  astore_1
   13:  monitorenter
   14:  aload_0
   15:  getfield    #12; //Field bitmap$0:I
   18:  iconst_1
   19:  iand
   20:  iconst_0
   21:  if_icmpne   39
   24:  aload_0
   25:  iconst_0
   26:  putfield    #14; //Field x:I
   29:  aload_0
   30:  aload_0
   31:  getfield    #12; //Field bitmap$0:I
   34:  iconst_1
   35:  ior
   36:  putfield    #12; //Field bitmap$0:I
   39:  getstatic   #20; //Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
   42:  pop
   43:  aload_1
   44:  monitorexit
   45:  aload_0
   46:  getfield    #14; //Field x:I
   49:  ireturn
   50:  aload_1
   51:  monitorexit
   52:  athrow
  Exception table:
   from   to  target type
    14    45    50   any

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #26; //Method java/lang/Object."<init>":()V
   4:   return

}

As you can see, the normal val accessor is very short and will definitely be inlined, whereas the lazy val accessor is quite complex and (most importantly for concurrency) involves a synchronized block (the monitorenter/monitorexit instructions). You can also see the extra field that is generated by the compiler.

like image 151
Rüdiger Klaehn Avatar answered Nov 10 '22 05:11

Rüdiger Klaehn