斯卡拉懒惰val的(隐藏)成本是多少?
斯卡拉的一个便捷功能是lazy val
,其中val
的评估延迟到必要时(首次访问时)。斯卡拉懒惰val的(隐藏)成本是多少?
当然,一个lazy val
必须具有一些开销 - 地方斯卡拉必须跟踪的该值是否已经被评估和评价必须是同步的,因为多个线程可能会试图同时访问该值首次时间。
lazy val
的成本究竟是什么 - 是否存在一个与lazy val
相关的隐藏布尔标志,用于跟踪它是否已经被评估,什么是同步的,是否还有更多的成本?
此外,假设我这样做:
class Something {
lazy val (x, y) = { ... }
}
这是就等于拥有两个独立的lazy val
小号x
和y
还是我得到的开销只有一次,一对(x, y)
?
这是从scala mailing list拍摄,并给出了lazy
实施细则中的Java代码(而不是字节码):由
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的访问将触发元组评估。独立并且懒惰地(并且缓存)从元组中提取单个值。因此,上述双实例代码生成x
,y
和x$1
类型的字段Tuple2
。
给出由scala生成的bycode for懒惰,它可能会遇到线程安全问题,如双重检查锁定中提到的http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1
这个声明也是由mitch接受的回答发表的,并且由@iirekm驳斥:这个模式从java1.5开始就没有问题。 – 2012-07-15 15:24:14
Scala SIP-20提出了一个新的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中。因此,我认为你应该明智地注意Daniel Spiewak's observation:
懒惰val * *不*免费(或甚至便宜)。只有当你绝对需要懒惰的正确性而不是优化时才使用它。
使用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
值被刷新到内存中。这篇博客文章给出了an explanation。
我写了一个帖子关于这个问题https://dzone.com/articles/cost-laziness
在概括地说,惩罚是如此之小,实际上,你可以忽略它。
感谢您的基准。你还可以基于SIP-20提出的实现进行基准测试吗? – Turadg 2015-01-29 17:29:09
我认为自2007年Java版本发布以来,实现必须发生变化。只有一个同步块,位图$ 0字段在当前实现中是不稳定的(2.8)。 – 2010-06-15 18:09:42
是的 - 我应该更多地关注我发布的内容! – 2010-06-16 09:25:34
所以,基本上,这意味着首次访问懒惰值比直接值慢(甚至可能在奇怪的情况下创建死锁),但后续访问几乎不会慢于非惰性值。看起来这不是轻率的,只是用于真正昂贵的初始化。 – 2010-11-04 09:36:31