Jes*_*per 160 performance scala lazy-evaluation
Scala的一个便利功能是lazy val,a的评估val被延迟到必要时(首次访问时).
当然,lazy val必须有一些开销 - 在某处Scala必须跟踪该值是否已经被评估并且评估必须同步,因为多个线程可能会尝试同时第一次访问该值.
a的成本到底是什么?lazy val是否有一个隐藏的布尔标志与a关联,lazy val如果它已经被评估,是什么同步,是否有更多的成本?
另外,假设我这样做:
class Something {
lazy val (x, y) = { ... }
}
Run Code Online (Sandbox Code Playgroud)
这是否与两个单独的lazy vals 相同x,y或者我只获得一次开销,对于该对(x, y)?
oxb*_*kes 81
这是从scala邮件列表中获取的,并提供lazy了Java代码(而不是字节码)的实现细节:
class LazyTest {
lazy val msg = "Lazy"
}
Run Code Online (Sandbox Code Playgroud)
被编译为等同于以下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;
}
}
Run Code Online (Sandbox Code Playgroud)
Mit*_*ins 38
看起来编译器安排类级别位图int字段将多个惰性字段标记为已初始化(或不),并且如果位图的相关xor指示有必要,则初始化同步块中的目标字段.
使用:
class Something {
lazy val foo = getFoo
def getFoo = "foo!"
}
Run Code Online (Sandbox Code Playgroud)
生成示例字节码:
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
Run Code Online (Sandbox Code Playgroud)
在元组中初始化的值就像lazy val (x,y) = { ... }通过相同的机制嵌套缓存一样.元组结果被懒惰地评估和缓存,并且访问x或y将触发元组评估.从元组中提取单个值是独立且懒惰(并缓存)完成的.因此上述双实例化代码生成x,y和x$1类型的字段Tuple2.
Raf*_*ter 24
使用Scala 2.10,一个懒惰的值如:
class Example {
lazy val x = "Value";
}
Run Code Online (Sandbox Code Playgroud)
被编译为类似于以下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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,位图由a表示boolean.如果添加另一个字段,编译器会将字段的大小增加到能够表示至少2个值,即a byte.这只适用于大型课程.
但你可能想知道为什么这样有效?进入同步块时必须清除线程本地高速缓存,以便将非易失性x值刷新到内存中.这篇博客文章给出了解释.
Lei*_*and 11
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
}
}
Run Code Online (Sandbox Code Playgroud)
截至2013年6月,该SIP尚未获得批准.我希望它可能会被批准并包含在基于邮件列表讨论的Scala的未来版本中.因此,我认为你应该明智地注意Daniel Spiewak的观察:
Lazy val*不*免费(甚至便宜).只有在绝对需要懒惰才能获得正确性而不是优化时才使用它.
我写了一篇关于这个问题的帖子https://dzone.com/articles/cost-laziness
简而言之,惩罚是如此之小,以至于在实践中你可以忽略它.
| 归档时间: |
|
| 查看次数: |
23153 次 |
| 最近记录: |