Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Scala: The Cost of Laziness

DZone's Guide to

Scala: The Cost of Laziness

Considering using ''lazy'' in Scala? Unsure if doing so will cost you efficiency? If so, it's important to look at what's under the hood of the ''lazy'' keyword.

· Performance Zone ·
Free Resource

Sensu is an open source monitoring event pipeline. Try it today.

Have you ever wondered about the performance impact of using a lazy keyword in Scala? Recently, I had a dispute with my colleagues about it. It resulted in a set of microbenchmarks with quite interesting results which I share in this article.

Before going to the benchmark results, let’s try to understand what can cause any performance penalty. For my JMH benchmark, I created a very simple Scala class with a single lazy value in it:

@State(Scope.Benchmark)
classLazyValCounterProvider {
  lazy val counter=SlowInitializer.createCounter()
}

Now, let’s take a look at what is hidden under the hood of the lazy keyword. At first, we need to compile given code with Scala and then it can be decompiled to correspondent Java code. I used JD decompiler and here is the output (I skip @ScalaSignature’s content for the sake of readability):

@State(Scope.Benchmark)
@ScalaSignature(bytes="...")
publicclassLazyValCounterProvider {
  private SlowInitializer.Counter counter;
  private volatile boolean bitmap$0;

  privateSlowInitializer.Counter counter$lzycompute() {
    synchronized(this) {
      if(!this.bitmap$0) {
        this.counter = SlowInitializer.createCounter();
        this.bitmap$0 = true;
        return this.counter;
      }
    }
  }

  public SlowInitializer.Counter counter() {
    return this.bitmap$0 ? this.counter : counter$lzycompute();
  }
}

As it’s seen, the lazy keyword is translated to a classical double-checked locking idiom for delayed initialization. Thus, most of the time, the only performance penalty may come from a single volatile read per lazy val read (except for the time it takes to initialize lazy val instance since its very first interaction). Let’s finally measure its impact in numbers.

My very simple JMH-based microbenchmark is shown below:

public class LazyValsBenchmarks {
  @Benchmark
  public long baseline(ValCounterProvider eagerProvider) {
    return eagerProvider.counter().incrementAndGet();
  }

  @Benchmark
  public long lazyValCounter(LazyValCounterProvider lazyProvider) {
    return lazyProvider.counter().incrementAndGet();
  }
}

In a baseline method, I measure the thoughput of accessing a final counter object and incrementing an integer value by 1. As we’ve just found out, the main benchmark method – lazyValCounter – also does one volatile read in addition to what the baseline method does.

Note: All measurements are performed on MBA with Core i5 1.7GHz CPU.

Benchmark results

Benchmark results

All results are obtained with JMH running in throughput mode measuring op(s). Each JMH session contained 10 samples and lasted for 50 seconds. I performed six measurements with the following options:

Exp # JVM Mode Threads # Benchmark Score Score error
1 Client VM 1 baseline 412277751.619 8116731.382
1 Client VM 1 lazyValCounter 352209296.485 6695318.185
2 Client VM 2 baseline 542605885.932 15340285.497
2 Client VM 2 lazyValCounter 383013643.710 53639006.105
3 Client VM 4 baseline 551105008.767 5085834.663
3 Client VM 4 lazyValCounter 394175424.898 3890422.327
4 Server VM 1 baseline 407010942.139 9004641.910
4 Server VM 1 lazyValCounter 341478430.115 18183144.277
5 Server VM 2 baseline 531472448.578 22779859.685
5 Server VM 2 lazyValCounter 428898429.124 24720626.198
6 Server VM 4 baseline 549568334.970 12690164.639
6 Server VM 4 lazyValCounter 374460712.017 17742852.788

The benchmark results show that the lazy performance penalty is quite small and can be ignored in most cases. Roughly speaking, the overhead is comparable with compound assignment operation time.

For further reading on the subject, I recommend SIP 20 – Improved Lazy Vals Initialization. It contains an interesting in-depth analysis of existing issues with lazy initialization implementation in Scala.

Sensu: workflow automation for monitoring. Learn more—download the whitepaper.

Topics:
lazy ,scala ,performance

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}