복붙노트

[SCALA] 스칼라의 게으른 발의 (숨겨진) 비용은 무엇입니까?

SCALA

스칼라의 게으른 발의 (숨겨진) 비용은 무엇입니까?

스칼라의 한 편리한 기능은 필요의 때까지 발의 평가 (처음 액세스에서) 지연 게으른 발입니다.

물론, 게으른 발은 약간의 오버 헤드를 가지고 있어야합니다 - 어딘가 스칼라 값이 이미 평가되었으며, 여러 스레드가 동시에 처음으로 값을 액세스하려고 할 수 있기 때문에 평가가 동기화해야하는지 여부를 추적해야합니다.

정확하게 동기화하고 더 이상 비용이 있습니다 무엇, 그것은 평가되었는지의 경우 추적 할 게으른 발과 관련된 숨겨진 부울 플래그가있다 - 정확히 게으른 발의 비용은 무엇입니까?

또한, 나는 이렇게 생각 :

class Something {
    lazy val (x, y) = { ... }
}

이 두 개의 별도의 게으른 놈들의 X를 가진 동일와 y가 아니면 쌍 (x, y)에 대한 한 번만 오버 헤드를받을 수 있나요?

해결법

  1. ==============================

    1.이 스칼라 메일 링리스트에서 가져온 Java 코드 (오히려 바이트 이상)의 측면에서 게으른의 구현 세부 사항을 제공한다 :

    이 스칼라 메일 링리스트에서 가져온 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;
      }
    
    }
    
  2. ==============================

    2.컴파일러는 초기화 (여부)로서 플래그 여러 지연 필드 클래스 레벨 맵 INT 필드의 배열 및 비트 맵의 ​​적절한 XOR이 필요하다고되어 있으면 동기 블록의 타겟 필드를 초기화 같습니다.

    컴파일러는 초기화 (여부)로서 플래그 여러 지연 필드 클래스 레벨 맵 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
    

    지연 발 (X, Y) = {...}과 같은 튜플에 이니셜 값은 동일한 메커니즘을 통해 캐시를 중첩했다. 튜플 결과 느리게 평가 캐시이며, x 또는 y 중 하나의 액세스는 튜플 평가를 트리거한다. 튜플의 개별 값을 추출 독립적 느리게 (캐시 된) 수행된다. 따라서 상기 이중 인스턴스화 코드는 x, y 및 Tuple2 유형의 필드 1 $ X를 생성한다.

  3. ==============================

    3.스칼라 2.10, 게으른 값과 같은 :

    스칼라 2.10, 게으른 값과 같은 :

    class Example {
      lazy val x = "Value";
    }
    

    다음과 같은 자바 코드를 유사한 바이트 코드로 컴파일한다 :

    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;
        }
      }
    }
    

    비트 맵 부울로 표시합니다. 다른 필드를 추가 할 경우, 컴파일러는 바이트로 즉, 2 개 이상의 값을 표시 할 수있는에 필드의 크기를 증가시킬 것이다. 이것은 단지 거대한 클래스에 간다.

    이 작동하지만 왜 당신이 궁금 할 것이다? 비 휘발성 x 값 메모리에 플러시되도록 동기화 블록에 들어갈 때 스레드 로컬 캐시는 삭제되어야한다. 이 블로그 기사에 대한 설명을 제공합니다.

  4. ==============================

    4.스칼라 SIP-20는 더 정확 게으른 발의 새로운 구현을 제안하지만 ~ "현재"버전보다 25 % 느리게.

    스칼라 SIP-20는 더 정확 게으른 발의 새로운 구현을 제안하지만 ~ "현재"버전보다 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
      }
    }
    

    2013 년 6 월이 SIP는 승인되지 않았습니다. 나는 메일 링리스트의 논의를 기반으로 스칼라의 이후 버전에서는 승인을 포함 할 가능성이 있다고 예상한다. 따라서, 당신이 다니엘 스피 웍의 관찰에 유의하는 것이 현명 할 것 같습니다 :

  5. ==============================

    5.나는이 문제에 관한 https://dzone.com/articles/cost-laziness으로 게시물을 작성했습니다

    나는이 문제에 관한 https://dzone.com/articles/cost-laziness으로 게시물을 작성했습니다

    간단히 말해서, 페널티은 실제로 당신이 그것을 무시할 수 있도록 작다.

  6. ==============================

    6.http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1 이중 체크 잠금에서 언급 한 바와 같이 게으른 스칼라에 의해 생성 된 bycode을 감안할 때, 그것은 스레드 안전 문제를 겪을 수

    http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html?page=1 이중 체크 잠금에서 언급 한 바와 같이 게으른 스칼라에 의해 생성 된 bycode을 감안할 때, 그것은 스레드 안전 문제를 겪을 수

  7. from https://stackoverflow.com/questions/3041253/whats-the-hidden-cost-of-scalas-lazy-val by cc-by-sa and MIT license