Scala 的惰性 val 的(隐藏)成本是多少?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3041253/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What's the (hidden) cost of Scala's lazy val?
提问by Jesper
One handy feature of Scala is lazy val, where the evaluation of a valis delayed until it's necessary (at first access).
Scala 的一个方便的特性是lazy val, a 的计算val被延迟到有必要时(第一次访问)。
Of course, a lazy valmust have some overhead - somewhere Scala must keep track of whether the value has already been evaluated and the evaluation must be synchronized, because multiple threads might try to access the value for the first time at the same time.
当然,alazy val必须有一些开销 - Scala 必须在某处跟踪该值是否已经被评估并且评估必须同步,因为多个线程可能会尝试同时第一次访问该值。
What exactly is the cost of a lazy val- is there a hidden boolean flag associated with a lazy valto keep track if it has been evaluated or not, what exactly is synchronized and are there any more costs?
a 的成本究竟是多少lazy val- 是否有一个隐藏的布尔标志与 a 相关联以lazy val跟踪它是否已被评估,究竟同步了什么以及是否有更多成本?
In addition, suppose I do this:
另外,假设我这样做:
class Something {
lazy val (x, y) = { ... }
}
Is this the same as having two separate lazy vals xand yor do I get the overhead only once, for the pair (x, y)?
这是否与有两个单独的lazy vals相同x,y或者我是否只获得一次开销,对于这对(x, y)?
采纳答案by oxbow_lakes
This is taken from the scala mailing listand gives implementation details of lazyin terms of Java code (rather than bytecode):
这取自scala 邮件列表,并给出lazy了 Java 代码(而不是字节码)的实现细节:
class LazyTest {
lazy val msg = "Lazy"
}
is compiled to something equivalent to the following Java code:
被编译为与以下 Java 代码等效的内容:
class LazyTest {
public int bitmapclass Something {
lazy val foo = getFoo
def getFoo = "foo!"
}
;
private String msg;
public String msg() {
if ((bitmap 0 aload_0 [this]
1 getfield blevins.example.Something.bitmapclass Example {
lazy val x = "Value";
}
: 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.bitmappublic class Example {
private String x;
private volatile boolean bitmapclass 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
}
}
;
public String x() {
if(this.bitmap##代码## == true) {
return this.x;
} else {
return x$lzycompute();
}
}
private String x$lzycompute() {
synchronized(this) {
if(this.bitmap##代码## != true) {
this.x = "Value";
this.bitmap##代码## = true;
}
return this.x;
}
}
}
: 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##代码## : int [15]
37 iconst_1
38 ior
39 putfield blevins.example.Something.bitmap##代码## : 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
& 1) == 0) {
synchronized (this) {
if ((bitmap##代码## & 1) == 0) {
synchronized (this) {
msg = "Lazy";
}
}
bitmap##代码## = bitmap##代码## | 1;
}
}
return msg;
}
}
回答by Mitch Blevins
It looks like the compiler arranges for a class-level bitmap int field to flag multiple lazy fields as initialized (or not) and initializes the target field in a synchronized block if the relevant xor of the bitmap indicates it is necessary.
看起来编译器会安排一个类级别的位图 int 字段来将多个惰性字段标记为已初始化(或未初始化),并在位图的相关异或指示有必要时初始化同步块中的目标字段。
Using:
使用:
##代码##produces sample bytecode:
生成示例字节码:
##代码##Values initialed in tuples like lazy val (x,y) = { ... }have nested caching via the same mechanism. The tuple result is lazily evaluated and cached, and an access of either x or y will trigger the tuple evaluation. Extraction of the individual value from the tuple is done independently and lazily (and cached). So the above double-instantiation code generates an x, y, and an x$1field of type Tuple2.
在元组中初始化的值,例如lazy val (x,y) = { ... }通过相同的机制嵌套缓存。元组结果被延迟评估和缓存,访问 x 或 y 将触发元组评估。从元组中提取单个值是独立且懒惰地(并缓存)完成的。所以上面的双实例化代码生成了一个x, y, 和一个x$1类型为 的字段Tuple2。
回答by Rafael Winterhalter
With Scala 2.10, a lazy value like:
使用 Scala 2.10,一个惰性值如:
##代码##is compiled to byte code that resembles the following Java code:
被编译为类似于以下 Java 代码的字节码:
##代码##Note that the bitmap is represented by a boolean. If you add another field, the compiler will increase the size of the field to being able to represent at least 2 values, i.e. as a byte. This just goes on for huge classes.
请注意,位图由 a 表示boolean。如果添加另一个字段,编译器将增加该字段的大小以能够表示至少 2 个值,即作为byte. 这仅适用于大型课程。
But you might wonder why this works? The thread-local caches must be cleared when entering a synchronized block such that the non-volatile xvalue is flushed into memory. This blog article gives an explanation.
但你可能想知道为什么会这样?进入同步块时必须清除线程本地缓存,以便将非易失性x值刷新到内存中。这篇博客文章给出了解释。
回答by Leif Wickland
Scala SIP-20proposes a new implementation of lazy val, which is more correct but ~25% slower than the "current" version.
Scala SIP-20提出了一个新的惰性 val 实现,它更正确,但比“当前”版本慢约 25%。
The proposed implementationlooks like:
该建议的实施是这样的:
##代码##As of June 2013 this SIP hasn't been approved. I expect that it's likely to be approved and included in a future version of Scala based on the mailing list discussion. Consequently, I think you'd be wise to heed Daniel Spiewak's observation:
截至 2013 年 6 月,该 SIP 尚未获得批准。我希望根据邮件列表的讨论,它很可能会被批准并包含在 Scala 的未来版本中。因此,我认为您应该注意Daniel Spiewak 的观察:
Lazy val is *not* free (or even cheap). Use it only if you absolutely need laziness for correctness, not for optimization.
Lazy val * 不* 免费(甚至便宜)。仅当您绝对需要为正确性而不是为了优化而懒惰时才使用它。
回答by Roman
I've written a post with regard to this issue https://dzone.com/articles/cost-laziness
我写了一篇关于这个问题的帖子https://dzone.com/articles/cost-laziness
In nutshell, the penalty is so small that in practice you can ignore it.
简而言之,惩罚很小,在实践中你可以忽略它。
回答by Huy Le
given the bycode generated by scala for lazy, it can suffer thread safety problem as mentioned in double check locking http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1
鉴于由 Scala 为惰性生成的副代码,它可能会遇到双重检查锁定中提到的线程安全问题http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1

