복붙노트

[SCALA] 중첩 된 방법의 비용

SCALA

중첩 된 방법의 비용

스칼라에서 하나의 다른 방법 안에 방법을 정의 할 수 있습니다. 이 정의 블록의 내부에 사용 자신의 범위를 제한한다. 나는 몇 고차 함수를 사용하는 코드의 가독성을 향상시키기 위해 그들을 사용합니다. 익명 함수 리터럴과는 달리,이 날을에 전달하기 전에 그들에게 의미있는 이름을 부여 할 수 있습니다.

예를 들면 :

class AggregatedPerson extends HashSet[PersonRecord] {
  def mostFrequentName: String = {
    type NameCount = (String, Int)
    def moreFirst(a: NameCount, b: NameCount) = a._2 > b._2
    def countOccurrences(nameGroup: (String, List[PersonRecord])) =
      (nameGroup._1, nameGroup._2.size) 

    iterator.toList.groupBy(_.fullName).
      map(countOccurrences).iterator.toList.
      sortWith(moreFirst).head._1
  }
}

어떤 런타임 비용 때문에 나는 알고 있어야 중첩 된 메소드 정의의가 있습니까?

대답은 폐쇄에 대한 차이가 있습니까?

해결법

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

    1.compilaton 동안 중첩 기능과 MoveFirst를 countOccurences mostFrequentName가 동일한 레벨로 밖으로 이동한다. MoveFirst를 $ 1 countOccurences $ 1 : 그들은 컴파일러 합성 이름을 얻는다.

    compilaton 동안 중첩 기능과 MoveFirst를 countOccurences mostFrequentName가 동일한 레벨로 밖으로 이동한다. MoveFirst를 $ 1 countOccurences $ 1 : 그들은 컴파일러 합성 이름을 얻는다.

    당신이 인수리스트를 사용하지 말고, 다음 방법 중 하나를 참조 할 때 또한, 그것은 함수에 올려진다. 그래서 (countOccurences)를지도하는지도를 작성하는 것과 동일 ((A : (문자열, 목록 [PersonRecord])) => countOccurences의 (a)). 이 익명 함수는 더 앞으로 countOccurences의 $보다 아무것도하지 않는 별도의 클래스 AggregatedPerson $$ anonfun $ mostFrequentName $ 2로 컴파일됩니다.

    사이드 참고로하는 기능에있어서의 해제 공정은 에타 확장 불린다. 그것은 당신이 (당신의 예에서와 같이) 함수 유형이 예상되는 상황에서 인수 목록을 생략하면 트리거, 또는 당신이 사용하는 경우 _ 전체 인수 목록 대신에, 또는 각 인수 대신에 (발 F1 = countOccurences _한다 ; 발 F2 = countOccurences (_).

    코드가 폐쇄에 직접 있었다면, 당신은 당신의 스택 한 적은 메서드 호출 및 생성 한 적은 합성 방법을 것이다. 이것의 성능에 미치는 영향은 대부분의 경우 제로 될 가능성이 높습니다.

    나는 구조 코드에 대한 내용 인 유용한 도구로 찾아 귀하의 예를 매우 관용적 스칼라을 고려하십시오.

    또 다른 유용한 도구는 발을 초기화 작은 블록을 사용하고 있습니다 :

    val a = {
       val temp1, temp2 = ...
       f(temp1, temp2)
    }
    

    당신은 스칼라 코드가 JVM에 대한 준비 형태로 변환되는 방법을 정확하게 볼 수 scalac의 -print를 사용할 수 있습니다. Heres는 프로그램의 출력 :

    [[syntax trees at end of cleanup]]// Scala source: nested-method.scala
    package <empty> {
    
      class AggregatedPerson extends scala.collection.mutable.HashSet with ScalaObject {
        def mostFrequentName(): java.lang.String = AggregatedPerson.this.iterator().toList().groupBy({
          (new AggregatedPerson$$anonfun$mostFrequentName$1(AggregatedPerson.this): Function1)
        }).map({
          {
            (new AggregatedPerson$$anonfun$mostFrequentName$2(AggregatedPerson.this): Function1)
          }
        }, collection.this.Map.canBuildFrom()).$asInstanceOf[scala.collection.MapLike]().iterator().toList().sortWith({
          {
            (new AggregatedPerson$$anonfun$mostFrequentName$3(AggregatedPerson.this): Function2)
          }
        }).$asInstanceOf[scala.collection.IterableLike]().head().$asInstanceOf[Tuple2]()._1().$asInstanceOf[java.lang.String]();
        final def moreFirst$1(a: Tuple2, b: Tuple2): Boolean = scala.Int.unbox(a._2()).>(scala.Int.unbox(b._2()));
        final def countOccurrences$1(nameGroup: Tuple2): Tuple2 = new Tuple2(nameGroup._1(), scala.Int.box(nameGroup._2().$asInstanceOf[scala.collection.SeqLike]().size()));
        def this(): AggregatedPerson = {
          AggregatedPerson.super.this();
          ()
        }
      };
    
      @SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$1 extends scala.runtime.AbstractFunction1 {
        final def apply(x$1: PersonRecord): java.lang.String = x$1.fullName();
        final <bridge> def apply(v1: java.lang.Object): java.lang.Object = AggregatedPerson$$anonfun$mostFrequentName$1.this.apply(v1.$asInstanceOf[PersonRecord]());
        def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$1 = {
          AggregatedPerson$$anonfun$mostFrequentName$1.super.this();
          ()
        }
      };
    
      @SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$2 extends scala.runtime.AbstractFunction1 {
        final def apply(nameGroup: Tuple2): Tuple2 = AggregatedPerson$$anonfun$mostFrequentName$2.this.$outer.countOccurrences$1(nameGroup);
        <synthetic> <paramaccessor> private[this] val $outer: AggregatedPerson = _;
        final <bridge> def apply(v1: java.lang.Object): java.lang.Object = AggregatedPerson$$anonfun$mostFrequentName$2.this.apply(v1.$asInstanceOf[Tuple2]());
        def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$2 = {
          if ($outer.eq(null))
            throw new java.lang.NullPointerException()
          else
            AggregatedPerson$$anonfun$mostFrequentName$2.this.$outer = $outer;
          AggregatedPerson$$anonfun$mostFrequentName$2.super.this();
          ()
        }
      };
      @SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$3 extends scala.runtime.AbstractFunction2 {
        final def apply(a: Tuple2, b: Tuple2): Boolean = AggregatedPerson$$anonfun$mostFrequentName$3.this.$outer.moreFirst$1(a, b);
        <synthetic> <paramaccessor> private[this] val $outer: AggregatedPerson = _;
        final <bridge> def apply(v1: java.lang.Object, v2: java.lang.Object): java.lang.Object = scala.Boolean.box(AggregatedPerson$$anonfun$mostFrequentName$3.this.apply(v1.$asInstanceOf[Tuple2](), v2.$asInstanceOf[Tuple2]()));
        def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$3 = {
          if ($outer.eq(null))
            throw new java.lang.NullPointerException()
          else
            AggregatedPerson$$anonfun$mostFrequentName$3.this.$outer = $outer;
          AggregatedPerson$$anonfun$mostFrequentName$3.super.this();
          ()
        }
      }
    }
    
  2. ==============================

    2.작은 런타임 비용이있다. 당신은 여기 (긴 코드에 대한 사과)을 관찰 할 수있다 :

    작은 런타임 비용이있다. 당신은 여기 (긴 코드에 대한 사과)을 관찰 할 수있다 :

    object NestBench {
      def countRaw() = {
        var sum = 0
        var i = 0
        while (i<1000) {
          sum += i
          i += 1
          var j = 0
          while (j<1000) {
            sum += j
            j += 1
            var k = 0
            while (k<1000) {
              sum += k
              k += 1
              sum += 1
            }
          }
        }
        sum
      }
      def countClosure() = {
        var sum = 0
        var i = 0
        def sumI {
          sum += i
          i += 1
          var j = 0
          def sumJ {
            sum += j
            j += 1
            var k = 0
            def sumK {
              def sumL { sum += 1 }
              sum += k
              k += 1
              sumL
            }
            while (k<1000) sumK
          }
          while (j<1000) sumJ
        }
        while (i<1000) sumI
        sum
      }
      def countInner() = {
        var sum = 0
        def whileI = {
          def whileJ = {
            def whileK = {
              def whileL() = 1
              var ksum = 0
              var k = 0
              while (k<1000) { ksum += k; k += 1; ksum += whileL }
              ksum
            }
            var jsum = 0
            var j = 0
            while (j<1000) {
              jsum += j; j += 1
              jsum += whileK
            }
            jsum
          }
          var isum = 0
          var i = 0
          while (i<1000) {
            isum += i; i += 1
            isum += whileJ
          }
          isum
        }
        whileI
      }
      def countFunc() = {
        def summer(f: => Int)() = {
          var sum = 0
          var i = 0
          while (i<1000) {
            sum += i; i += 1
            sum += f
          }
          sum
        }
        summer( summer( summer(1) ) )()
      }
      def nsPerIteration(f:() => Int): (Int,Double) = {
        val t0 = System.nanoTime
        val result = f()
        val t1 = System.nanoTime
        (result , (t1-t0)*1e-9)
      }
      def main(args: Array[String]) {
        for (i <- 1 to 5) {
          val fns = List(countRaw _, countClosure _, countInner _, countFunc _)
          val labels = List("raw","closure","inner","func")
          val results = (fns zip labels) foreach (fl => {
            val x = nsPerIteration( fl._1 )
            printf("Method %8s produced %d; time/it = %.3f ns\n",fl._2,x._1,x._2)
          })
        }
      }
    }
    

    정수를 합산위한 네 가지 방법이 있습니다 :

    그리고 우리는 내부 루프에서 촬영 나노초의 관점에서 내 컴퓨터에 결과를 참조하십시오

    scala> NestBench.main(Array[String]())
    Method      raw produced -1511174132; time/it = 0.422 ns
    Method  closure produced -1511174132; time/it = 2.376 ns
    Method    inner produced -1511174132; time/it = 0.402 ns
    Method     func produced -1511174132; time/it = 0.836 ns
    Method      raw produced -1511174132; time/it = 0.418 ns
    Method  closure produced -1511174132; time/it = 2.410 ns
    Method    inner produced -1511174132; time/it = 0.399 ns
    Method     func produced -1511174132; time/it = 0.813 ns
    Method      raw produced -1511174132; time/it = 0.411 ns
    Method  closure produced -1511174132; time/it = 2.372 ns
    Method    inner produced -1511174132; time/it = 0.399 ns
    Method     func produced -1511174132; time/it = 0.813 ns
    Method      raw produced -1511174132; time/it = 0.411 ns
    Method  closure produced -1511174132; time/it = 2.370 ns
    Method    inner produced -1511174132; time/it = 0.399 ns
    Method     func produced -1511174132; time/it = 0.815 ns
    Method      raw produced -1511174132; time/it = 0.412 ns
    Method  closure produced -1511174132; time/it = 2.357 ns
    Method    inner produced -1511174132; time/it = 0.400 ns
    Method     func produced -1511174132; time/it = 0.817 ns
    

    그래서, 결론은 다음과 같습니다 중첩 기능은 정말 간단 경우 모두에서 당신을 다치게하지 않습니다 - JVM은 호출 (따라서 원료 및 내부주고 같은 시간) 인라인 될 수 있다는 것을 알아낼 것이다. 당신은 더 많은 기능 접근을 경우,이 함수의 호출은 완전히 무시 될 수는 없지만, 소요되는 시간은 하고서 (추가 통화 당 약 0.4 NS)입니다. 당신은 폐쇄를 많이 사용하는 경우, 그들이 하나의 가변 변수에 기록이 경우 적어도 통화 당 1 NS 같은의 오버 헤드를 제공 닫는.

    당신은 다른 질문에 대한 답을 찾을 수 위의 코드를 수정할 수 있지만 결론은 "아무런 처벌"단지 걱정 "까지 사이에 이르기까지 매우 빠르고, 모든 점이다 그렇지 않으면 최소한의 일이 매우 협소 한 내부 루프에 대한 해야 할 것".

    (추신 비교를 위해, 단일의 작은 객체의 생성 내 머신 ~ 4 NS 걸린다.)

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

    3.년 1 월 2014 년 현재

    년 1 월 2014 년 현재

    현재 벤치 마크는 ~ 3 세이며 핫스팟 컴파일러는 크게 진화했다. 나는 또한 벤치 마크를 수행하기 위해 구글 캘리퍼스를 사용하고 있습니다.

    import com.google.caliper.SimpleBenchmark
    
    class Benchmark extends SimpleBenchmark {
        def timeRaw(reps: Int) = {
            var i = 0
            var result = 0L
            while (i < reps) {
                result += 0xc37e ^ (i * 0xd5f3)
                i = i + 1
            }
            result
        }
    
        def normal(i: Int): Long = 0xc37e ^ (i * 0xd5f3)
        def timeNormal(reps: Int) = {
            var i = 0
            var result = 0L
            while (i < reps) {
                result += normal(i)
                i = i + 1
            }
            result
        }
    
        def timeInner(reps: Int) = {
            def inner(i: Int): Long = 0xc37e ^ (i * 0xd5f3)
    
            var i = 0
            var result = 0L
            while (i < reps) {
                result += inner(i)
                i = i + 1
            }
            result
        }
    
        def timeClosure(reps: Int) = {
            var i = 0
            var result = 0L
            val closure = () => result += 0xc37e ^ (i * 0xd5f3)
            while (i < reps) {
                closure()
                i = i + 1
            }
            result
        }
    
        def normal(i: Int, j: Int, k: Int, l: Int): Long = i ^ j ^ k ^ l 
        def timeUnboxed(reps: Int) = {
            var i = 0
            var result = 0L
            while (i < reps) {
                result += normal(i,i,i,i)
                i = i + 1
            }
            result
        }
    
        val closure = (i: Int, j: Int, k: Int, l: Int) => (i ^ j ^ k ^ l).toLong 
        def timeBoxed(reps: Int) = {
            var i = 0
            var result = 0L
            while (i < reps) {
                closure(i,i,i,i)
                i = i + 1
            }
            result
        }
    }
    
    benchmark     ns linear runtime
       Normal  0.576 =
          Raw  0.576 =
        Inner  0.576 =
      Closure  0.532 =
      Unboxed  0.893 =
        Boxed 15.210 ==============================
    

    무엇 매우 놀라운 것은 그 폐쇄 시험은 빨리 다른 사람보다가 4Ns에 대해 완성 된 것입니다. 이 핫스팟의 특질 대신 실행 환경 것 같다, 다수의 실행이 같은 추세를 돌아왔다.

    수행 권투는 큰 성능 저하는 것을 클로저를 사용하면, 약 기본 수학 연산을 많이 할 정도로 언 박싱 및 reboxing 하나를 수행 할 3.579ns 걸립니다. 이 특정 위치에서 가지 작업이 새로운 최적화에서 수행되고 더 잘 얻을 수 있습니다. 일반적인 경우, 권투 miniboxing에 의해 완화 될 수 있습니다.

    편집하다: 정말 여기에 도움이되지 않는 새로운 최적화, 그것은 0.1 ns의 빠른 0.1 ns의 느린 폐쇄을하고 박스형 :

     benchmark     ns linear runtime
           Raw  0.574 =
        Normal  0.576 =
         Inner  0.575 =
       Closure  0.645 =
       Unboxed  0.889 =
         Boxed 15.107 ==============================
    

    magarciaEPFL / 스칼라에서 2.11.0-20131209-220002-9587726041 수행

    java version "1.7.0_51"
    Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)
    
    Scala compiler version 2.10.3 -- Copyright 2002-2013, LAMP/EPFL
    
  4. from https://stackoverflow.com/questions/2482326/the-cost-of-nested-methods by cc-by-sa and MIT license