Scala 게으른 발의 (숨겨진) 비용은 얼마입니까?
Scala의 편리한 기능 중 하나 lazy val
는 val
입니다 (필수에 액세스 할 때까지).
물론, lazy val
약간의 오버 헤드 가 있어야합니다. 어딘가에 Scala는 값이 이미 평가되었는지와 평가를 동기화해야하는지 추적해야합니다. 여러 스레드가 동시에 처음으로 값에 액세스하려고 시도 할 수 있기 때문입니다.
정확히 비용은 얼마입니까-평가 여부에 따라 추적을 유지하기 위해 lazy val
숨겨진 부울 플래그 lazy val
가 있습니까? 정확히 동기화되고 더 많은 비용이 있습니까?
또한 내가 이것을한다고 가정 해보십시오.
class Something {
lazy val (x, y) = { ... }
}
이 두 개의 분리 된 것으로 동일 lazy val
들 x
과 y
한 쌍을 위해, 또는 내가 한 번만 오버 헤드를받을 수 있나요 (x, y)
?
이것은 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;
}
}
컴파일러는 클래스 수준의 비트 맵 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
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$1
field of type Tuple2
.
With Scala 2.10, a lazy value like:
class Example {
lazy val x = "Value";
}
is compiled to byte code that resembles the following Java code:
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;
}
}
}
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.
But you might wonder why this works? The thread-local caches must be cleared when entering a synchronized block such that the non-volatile x
value is flushed into memory. This blog article gives an explanation.
Scala SIP-20 proposes a new implementation of lazy val, which is more correct but ~25% slower than the "current" version.
The proposed implementation looks like:
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
}
}
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:
Lazy val is *not* free (or even cheap). Use it only if you absolutely need laziness for correctness, not for optimization.
I've written a post with regard to this issue https://dzone.com/articles/cost-laziness
In nutshell, the penalty is so small that in practice you can ignore it.
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
참고URL : https://stackoverflow.com/questions/3041253/whats-the-hidden-cost-of-scalas-lazy-val
'development' 카테고리의 다른 글
Visual Studio 호스팅 프로세스의 목적은 무엇입니까? (0) | 2020.06.03 |
---|---|
Flexbox 및 Twitter 부트 스트랩 (또는 유사한 프레임 워크) (0) | 2020.06.03 |
선호하는 PHP 배포 전략은 무엇입니까? (0) | 2020.06.03 |
서버에서 결과를 수신 할 때 전송 레벨 오류가 발생했습니다. (0) | 2020.06.03 |
Java는 파일 크기를 효율적으로 얻습니다. (0) | 2020.06.03 |