Scalaの便利な機能の1つはlazy val
で、val
の評価は必要になるまで(最初のアクセスで)遅延されます。
もちろん、lazy val
にはオーバーヘッドが必要です-複数のスレッドがアクセスしようとする可能性があるため、どこかScalaは値が既に評価され、評価が同期されているかどうかを追跡する必要があります同時に最初の値。
lazy val
の正確なコストは何ですか?-評価されているかどうかを追跡するためにlazy val
に関連付けられた隠しブール値フラグはありますか?
さらに、私がこれを行うと仮定します:
class Something {
lazy val (x, y) = { ... }
}
これは、2つの別個のlazy val
s x
とy
を持っていることと同じですか、または(x, y)
ペアに対して一度だけオーバーヘッドを取得しますか?
これは scalaメーリングリスト から取得され、Javaコード(バイトコードではなく))に関してlazy
の実装の詳細を示します。
class LazyTest {
lazy val msg = "Lazy"
}
以下のJavaコードと同等のものにコンパイルされます:
class LazyTest {
public int bitmap$0;
private String msg;
public String msg() {
if ((bitmap$0 & 1) == 0) {
synchronized (this) {
if ((bitmap$0 & 1) == 0) {
synchronized (this) {
msg = "Lazy";
}
}
bitmap$0 = bitmap$0 | 1;
}
}
return msg;
}
}
コンパイラは、クラスレベルのビットマップintフィールドを配置して複数の遅延フィールドを初期化済み(または未初期化)としてフラグを立て、ビットマップの関連するxorが必要であることを示す場合、同期ブロック内のターゲットフィールドを初期化するようです。
を使用して:
_class Something {
lazy val foo = getFoo
def getFoo = "foo!"
}
_
サンプルバイトコードを生成します。
_ 0 aload_0 [this]
1 getfield blevins.example.Something.bitmap$0 : int [15]
4 iconst_1
5 iand
6 iconst_0
7 if_icmpne 48
10 aload_0 [this]
11 dup
12 astore_1
13 monitorenter
14 aload_0 [this]
15 getfield blevins.example.Something.bitmap$0 : int [15]
18 iconst_1
19 iand
20 iconst_0
21 if_icmpne 42
24 aload_0 [this]
25 aload_0 [this]
26 invokevirtual blevins.example.Something.getFoo() : Java.lang.String [18]
29 putfield blevins.example.Something.foo : Java.lang.String [20]
32 aload_0 [this]
33 aload_0 [this]
34 getfield blevins.example.Something.bitmap$0 : int [15]
37 iconst_1
38 ior
39 putfield blevins.example.Something.bitmap$0 : int [15]
42 getstatic scala.runtime.BoxedUnit.UNIT : scala.runtime.BoxedUnit [26]
45 pop
46 aload_1
47 monitorexit
48 aload_0 [this]
49 getfield blevins.example.Something.foo : Java.lang.String [20]
52 areturn
53 aload_1
54 monitorexit
55 athrow
_
lazy val (x,y) = { ... }
のようなタプルで初期化された値は、同じメカニズムを介してキャッシュをネストしています。タプルの結果は遅延評価およびキャッシュされ、xまたはyのいずれかのアクセスはタプル評価をトリガーします。 Tupleからの個々の値の抽出は、独立して遅延して行われます(そしてキャッシュされます)。したがって、上記の二重インスタンスコードは、x
、y
、および_x$1
_型の_Tuple2
_フィールドを生成します。
Scala 2.10、次のような遅延値:
class Example {
lazy val x = "Value";
}
次のJavaコードに似たバイトコードにコンパイルされます。
public class Example {
private String x;
private volatile boolean bitmap$0;
public String x() {
if(this.bitmap$0 == true) {
return this.x;
} else {
return x$lzycompute();
}
}
private String x$lzycompute() {
synchronized(this) {
if(this.bitmap$0 != true) {
this.x = "Value";
this.bitmap$0 = true;
}
return this.x;
}
}
}
ビットマップはboolean
で表されることに注意してください。別のフィールドを追加すると、コンパイラはフィールドのサイズを増やして、少なくとも2つの値、つまりbyte
として表現できるようにします。これは、巨大なクラスでのみ行われます。
しかし、なぜこれが機能するのか疑問に思うかもしれません。不揮発性x
値がメモリにフラッシュされるように、同期ブロックに入るとき、スレッドローカルキャッシュをクリアする必要があります。このブログ記事では 説明 を提供しています。
Scala SIP-2 は、lazy valの新しい実装を提案します。これは、より正確ですが、「現在の」バージョンよりも〜25%遅いです。
提案された実装 は次のようになります。
class LazyCellBase { // in a Java file - we need a public bitmap_0
public static AtomicIntegerFieldUpdater<LazyCellBase> arfu_0 =
AtomicIntegerFieldUpdater.newUpdater(LazyCellBase.class, "bitmap_0");
public volatile int bitmap_0 = 0;
}
final class LazyCell extends LazyCellBase {
import LazyCellBase._
var value_0: Int = _
@tailrec final def value(): Int = (arfu_0.get(this): @switch) match {
case 0 =>
if (arfu_0.compareAndSet(this, 0, 1)) {
val result = 0
value_0 = result
@tailrec def complete(): Unit = (arfu_0.get(this): @switch) match {
case 1 =>
if (!arfu_0.compareAndSet(this, 1, 3)) complete()
case 2 =>
if (arfu_0.compareAndSet(this, 2, 3)) {
synchronized { notifyAll() }
} else complete()
}
complete()
result
} else value()
case 1 =>
arfu_0.compareAndSet(this, 1, 2)
synchronized {
while (arfu_0.get(this) != 3) wait()
}
value_0
case 2 =>
synchronized {
while (arfu_0.get(this) != 3) wait()
}
value_0
case 3 => value_0
}
}
2013年6月現在、これはSIPは承認されていません。将来のバージョンでScalaに基づいて承認され、含まれると思われます結果として、あなたは注意するのが賢明だと思います ダニエル・スピーワクの観察 :
Lazy valは無料ではありません(または安価です)。最適化のためではなく、正確性のために絶対に怠absolutelyが必要な場合にのみ使用してください。
この問題に関する投稿を書いた https://dzone.com/articles/cost-laziness
一言で言えば、ペナルティは非常に小さいため、実際には無視できます。