Scala的一个便利功能是 lazy val
,其中 val
的评估被推迟到必要时(首次访问时) .
当然, lazy val
必须有一些开销 - 某处Scala必须跟踪该值是否已经被评估并且评估必须同步,因为多个线程可能会同时尝试第一次访问该值 .
lazy val
的成本究竟是什么 - 是否有一个隐藏的布尔标志与 lazy val
相关联,以便跟踪它是否已被评估,究竟是什么同步并且还有更多的成本?
另外,假设我这样做:
class Something {
lazy val (x, y) = { ... }
}
这是否与两个单独的 lazy val
s x
和 y
相同,或者我只获得一次开销,对于 (x, y)
对?
6 回答
这取自scala mailing list,并根据Java代码(而不是字节码)提供
lazy
的实现细节:被编译为等同于以下Java代码的东西:
看起来编译器安排类级别位图int字段将多个惰性字段标记为已初始化(或不),并且如果位图的相关xor指示有必要,则初始化同步块中的目标字段 .
使用:
生成示例字节码:
在像
lazy val (x,y) = { ... }
这样的元组中初始化的值通过相同的机制嵌套了缓存 . 元组结果被懒惰地评估和缓存,并且访问x或y将触发元组评估 . 从元组中提取单个值是独立且懒惰(并缓存)完成的 . 因此,上面的双实例化代码生成x
,y
和x$1
类型的x$1
字段 .使用Scala 2.10,一个懒惰的值如:
被编译为类似于以下Java代码的字节代码:
请注意,位图由
boolean
表示 . 如果添加另一个字段,编译器会将字段的大小增加到能够表示至少2个值,即作为byte
. 这只适用于大型课程 .但你可能想知道为什么这样有效?进入同步块时必须清除线程本地高速缓存,以便将非易失性
x
值刷新到内存中 . 这篇博客文章给出了an explanation .Scala SIP-20提出了一个lazy val的新实现,它更正确但比"current"版本慢约25% .
proposed implementation看起来像:
截至2013年6月,根据邮件列表讨论,此SIP可能不会被批准并包含在Scala的未来版本中 . 因此,我认为你应该明智地注意Daniel Spiewak's observation:
我写了一篇关于这个问题的帖子https://dzone.com/articles/cost-laziness
简而言之,惩罚是如此之小,以至于在实践中你可以忽略它 .
给出scala为懒惰生成的bycode,它会遇到双重检查锁定http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1中提到的线程安全问题