Không có câu trả lời nào khác đề cập đến lý do chính cho sự khác biệt về tốc độ, đó là zipped
phiên bản tránh được 10.000 phân bổ. Như một vài câu trả lời khác cần lưu ý, zip
phiên bản liên quan đến một mảng trung gian, trong khi zipped
phiên bản thì không, nhưng việc phân bổ một mảng cho 10.000 phần tử không phải là điều khiến zip
phiên bản tệ hơn nhiều, đó là 10.000 bộ dữ liệu ngắn. đang được đưa vào mảng đó. Chúng được đại diện bởi các đối tượng trên JVM, vì vậy bạn đang thực hiện một loạt các phân bổ đối tượng cho những thứ mà bạn sẽ vứt bỏ ngay lập tức.
Phần còn lại của câu trả lời này chỉ đi vào chi tiết hơn một chút về cách bạn có thể xác nhận điều này.
Điểm chuẩn tốt hơn
Bạn thực sự muốn sử dụng một khung công tác như jmh để thực hiện bất kỳ loại điểm chuẩn nào có trách nhiệm trên JVM, và ngay cả phần có trách nhiệm cũng khó, mặc dù việc thiết lập jmh không quá tệ. Nếu bạn có mộtproject/plugins.sbt
như thế này:
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.7")
Và một build.sbt
như thế này (Tôi đang sử dụng 2.11.8 vì bạn đề cập đến những gì bạn đang sử dụng):
scalaVersion := "2.11.8"
enablePlugins(JmhPlugin)
Sau đó, bạn có thể viết điểm chuẩn của bạn như thế này:
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)
}
Và chạy nó với 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
Điều đó cho thấy rằng zipped
phiên bản nhận được thông lượng nhiều hơn khoảng 80%, có thể ít nhiều giống với số đo của bạn.
Đo lường phân bổ
Bạn cũng có thể yêu cầu jmh đo lường phân bổ với -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
Ở đâu gc.alloc.rate.norm
có lẽ là phần thú vị nhất, cho thấy zip
phiên bản được phân bổ nhiều gấp ba lầnzipped
.
Thực hiện bắt buộc
Nếu tôi biết rằng phương pháp này sẽ được gọi trong bối cảnh cực kỳ nhạy cảm với hiệu suất, có lẽ tôi sẽ thực hiện nó như thế này:
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
}
Lưu ý rằng không giống như các phiên bản được tối ưu hóa trong một trong những câu trả lời khác, sử dụng này while
thay vì một for
từ for
vẫn sẽ desugar vào Scala bộ sưu tập các hoạt động. Chúng ta có thể so sánh việc triển khai này ( withWhile
), triển khai ( nhưng không tại chỗ) của câu trả lời khác ( withFor
) và hai triển khai ban đầu:
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
Đó thực sự là một sự khác biệt rất lớn giữa các phiên bản mệnh lệnh và chức năng, và tất cả các chữ ký phương thức này hoàn toàn giống hệt nhau và việc triển khai có cùng một ngữ nghĩa. Nó không giống như các triển khai bắt buộc đang sử dụng nhà nước toàn cầu, vv Trong khi zip
vàzipped
phiên bản phiên bản dễ đọc hơn, cá nhân tôi không nghĩ có bất kỳ ý nghĩa nào trong đó các phiên bản bắt buộc chống lại "tinh thần Scala", và tôi sẽ không ngần ngại để sử dụng chúng cho mình.
Với bảng
Cập nhật: Tôi đã thêm một tabulate
triển khai vào điểm chuẩn dựa trên một nhận xét trong câu trả lời khác:
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))
}
Nó nhanh hơn nhiều so với các zip
phiên bản, mặc dù vẫn chậm hơn nhiều so với các phiên bản bắt buộc:
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
Đây là những gì tôi mong đợi, vì vốn dĩ không có gì đắt khi gọi hàm và bởi vì việc truy cập các phần tử mảng theo chỉ mục là rất rẻ.