Scala的延迟val有哪些“隐藏”的成本?
Scala的延迟val有哪些“隐藏”的成本?
Scala的一个方便的特性是lazy val
,其中val
的评估被延迟到它必须(在第一次访问时)才执行。
当然,一个lazy val
必须有一些开销-Scala必须在某处跟踪值是否已经被评估,并且评估必须同步,因为多个线程可能同时尝试访问值进行第一次访问。
那么lazy val
的成本到底是多少-是否有一个隐藏的布尔标志与lazy val
相关,以跟踪它是否已经被评估,具体的同步是什么,还有其他成本吗?
另外,假设我这样做:
class Something { lazy val (x, y) = { ... } }
这是否与拥有两个单独的lazy val
x
和y
相同,或者我只获得一次开销,对于对(x,y)
成对的延迟值?
admin 更改状态以发布 2023年5月24日
看起来编译器安排了一个类级位图int字段来标记多个延迟字段是否已初始化,并在相关的异或位图指示需要时在同步块中初始化目标字段。
使用:
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将触发元组的评估。从元组中提取各个值是独立地和懒惰地完成的(并缓存)。因此上面的双重实例化代码会生成一个类型为Tuple2
的x
,y
和x$1
字段。
这是从Scala邮件列表中提取出来的内容,它描述了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; } }