다른 답변은 속도 차이의 주된 이유를 언급 zipped
하지 않습니다. 즉 버전이 10,000 튜플 할당을 피한다 는 것입니다 . 다른 답변의 몇으로 할 메모는 zip
그동안 버전은 중간 배열을 포함 zipped
버전은하지 않지만, 10,000 요소에 대한 배열을 할당하는 것은 무엇이 아닌 zip
버전을 훨씬 더-는 10,000 단명 튜플 사용자들은 그 배열에 넣고 있습니다. 이것들은 JVM의 객체로 표시되므로 즉시 버릴 물건에 대해 많은 객체 할당을 수행합니다.
이 답변의 나머지 부분에서는이를 확인할 수있는 방법에 대해 좀 더 자세히 설명합니다.
더 나은 벤치마킹
JVM에서 책임있는 벤치마킹을 수행하기 위해 jmh 와 같은 프레임 워크를 사용하고 싶고 jmh 자체를 설정하는 것은 그리 나쁘지 않지만 책임감있는 부분은 어렵습니다. 당신이있는 경우 project/plugins.sbt
이 같은를 :
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
그리고 build.sbt
이와 같이 (나는 당신이 사용하고 있다고 언급했기 때문에 2.11.8을 사용하고 있습니다) :
scalaVersion := "2.11.8"
enablePlugins(JmhPlugin)
그런 다음 벤치 마크를 다음과 같이 작성할 수 있습니다.
package zipped_bench
import org.openjdk.jmh.annotations._
@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class ZippedBench {
val arr1 = Array.fill(10000)(math.random)
val arr2 = Array.fill(10000)(math.random)
def ES(arr: Array[Double], arr1: Array[Double]): Array[Double] =
arr.zip(arr1).map(x => x._1 + x._2)
def ES1(arr: Array[Double], arr1: Array[Double]): Array[Double] =
(arr, arr1).zipped.map((x, y) => x + y)
@Benchmark def withZip: Array[Double] = ES(arr1, arr2)
@Benchmark def withZipped: Array[Double] = ES1(arr1, arr2)
}
그리고 그것을 실행하십시오 sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 zipped_bench.ZippedBench"
:
Benchmark Mode Cnt Score Error Units
ZippedBench.withZip thrpt 20 4902.519 ± 41.733 ops/s
ZippedBench.withZipped thrpt 20 8736.251 ± 36.730 ops/s
이는 zipped
버전이 처리량을 약 80 % 더 많이 얻었음을 보여줍니다 .
할당 측정
jmh에게 다음을 사용하여 할당을 측정하도록 요청할 수도 있습니다 -prof gc
.
Benchmark Mode Cnt Score Error Units
ZippedBench.withZip thrpt 5 4894.197 ± 119.519 ops/s
ZippedBench.withZip:·gc.alloc.rate thrpt 5 4801.158 ± 117.157 MB/sec
ZippedBench.withZip:·gc.alloc.rate.norm thrpt 5 1080120.009 ± 0.001 B/op
ZippedBench.withZip:·gc.churn.PS_Eden_Space thrpt 5 4808.028 ± 87.804 MB/sec
ZippedBench.withZip:·gc.churn.PS_Eden_Space.norm thrpt 5 1081677.156 ± 12639.416 B/op
ZippedBench.withZip:·gc.churn.PS_Survivor_Space thrpt 5 2.129 ± 0.794 MB/sec
ZippedBench.withZip:·gc.churn.PS_Survivor_Space.norm thrpt 5 479.009 ± 179.575 B/op
ZippedBench.withZip:·gc.count thrpt 5 714.000 counts
ZippedBench.withZip:·gc.time thrpt 5 476.000 ms
ZippedBench.withZipped thrpt 5 11248.964 ± 43.728 ops/s
ZippedBench.withZipped:·gc.alloc.rate thrpt 5 3270.856 ± 12.729 MB/sec
ZippedBench.withZipped:·gc.alloc.rate.norm thrpt 5 320152.004 ± 0.001 B/op
ZippedBench.withZipped:·gc.churn.PS_Eden_Space thrpt 5 3277.158 ± 32.327 MB/sec
ZippedBench.withZipped:·gc.churn.PS_Eden_Space.norm thrpt 5 320769.044 ± 3216.092 B/op
ZippedBench.withZipped:·gc.churn.PS_Survivor_Space thrpt 5 0.360 ± 0.166 MB/sec
ZippedBench.withZipped:·gc.churn.PS_Survivor_Space.norm thrpt 5 35.245 ± 16.365 B/op
ZippedBench.withZipped:·gc.count thrpt 5 863.000 counts
ZippedBench.withZipped:·gc.time thrpt 5 447.000 ms
… gc.alloc.rate.norm
어쩌면 가장 흥미로운 부분은 아마도 zip
버전이 3 배 이상 할당되고 있음을 보여줍니다 zipped
.
명령형 구현
이 방법이 성능에 매우 민감한 상황에서 호출 될 것임을 알고 있다면 다음과 같이 구현했을 것입니다.
def ES3(arr: Array[Double], arr1: Array[Double]): Array[Double] = {
val minSize = math.min(arr.length, arr1.length)
val newArr = new Array[Double](minSize)
var i = 0
while (i < minSize) {
newArr(i) = arr(i) + arr1(i)
i += 1
}
newArr
}
다른 답변 중 하나의 최적화 된 버전과 달리 이것은 Scala 수집 작업으로 탈당 할 것이므로 이후 while
대신에 사용 됩니다 . 이 구현 ( ), 다른 답변의 최적화 된 (제자리에없는) 구현 ( ) 및 두 가지 원래 구현을 비교할 수 있습니다 .for
for
withWhile
withFor
Benchmark Mode Cnt Score Error Units
ZippedBench.withFor thrpt 20 118426.044 ± 2173.310 ops/s
ZippedBench.withWhile thrpt 20 119834.409 ± 527.589 ops/s
ZippedBench.withZip thrpt 20 4886.624 ± 75.567 ops/s
ZippedBench.withZipped thrpt 20 9961.668 ± 1104.937 ops/s
그것은 명령형 버전과 기능 버전 사이에 큰 차이가 있으며 이러한 모든 메소드 서명은 정확히 동일하며 구현은 동일한 의미를 갖습니다. 명령형 구현이 전역 상태 등을 사용하는 것과는 다릅니다. zip
및 zipped
버전은 더 읽기 쉽지만 개인적으로 명령형 버전이 "스칼라의 정신"에 위배되는 의미가 있다고 생각하지 않으며 주저하지 않을 것입니다 직접 사용하십시오.
표로
업데이트 : tabulate
다른 답변의 의견을 기반으로 벤치 마크에 구현을 추가했습니다 .
def ES4(arr: Array[Double], arr1: Array[Double]): Array[Double] = {
val minSize = math.min(arr.length, arr1.length)
Array.tabulate(minSize)(i => arr(i) + arr1(i))
}
zip
명령형 버전보다 훨씬 느리지 만 버전 보다 훨씬 빠릅니다 .
Benchmark Mode Cnt Score Error Units
ZippedBench.withTabulate thrpt 20 32326.051 ± 535.677 ops/s
ZippedBench.withZip thrpt 20 4902.027 ± 47.931 ops/s
이것은 함수 호출에 본질적으로 비싸지 않으며 인덱스로 배열 요소에 액세스하는 것이 매우 저렴하기 때문에 내가 기대하는 것입니다.