Scala的一个方便的特性是lazy val
,在这个特性中,val
的求值会延迟到需要时(第一次访问时)。
当然,lazy val
必须有一些开销--在某个地方,Scala必须跟踪这个值是否已经被求值,并且求值必须是同步的,因为多个线程可能会同时尝试第一次访问这个值。
lazy val
的成本到底是多少-是否有一个隐藏的布尔标志与lazy val
相关联,用于跟踪它是否已被评估,同步的确切内容是什么,还有其他成本吗?
另外,假设我这样做:
class Something {
lazy val (x, y) = { ... }
}
这是否与拥有两个独立的lazy val
的x
和y
相同,或者对于(x, y)
对,我是否只获得一次开销?
发布于 2010-06-15 15:51:15
这段代码取自scala mailing list,并以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;
}
}
发布于 2010-06-15 07:15:39
看起来编译器安排了一个类级别的位图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
和一个Tuple2
类型的x$1
字段。
发布于 2014-05-25 22:47:26
在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://stackoverflow.com/questions/3041253
复制相似问题