ไม่มีคำตอบอื่น ๆ ที่พูดถึงเหตุผลหลักสำหรับความแตกต่างของความเร็วซึ่งเป็นzipped
เวอร์ชั่นที่หลีกเลี่ยงการจัดสรร 10,000 tuple ในฐานะที่เป็นคู่ของคำตอบอื่น ๆทำโน้ตzip
รุ่นเกี่ยวข้องกับอาร์เรย์กลางในขณะที่zipped
รุ่นไม่ได้ แต่การจัดสรรอาร์เรย์ 10,000 องค์ประกอบไม่ได้เป็นสิ่งที่ทำให้zip
รุ่นมากที่เลวร้ายยิ่งมัน 10,000 tuples สั้น ๆ ว่า กำลังถูกใส่เข้าไปในอาร์เรย์นั้น สิ่งเหล่านี้แสดงโดยวัตถุบน JVM ดังนั้นคุณจึงทำการจัดสรรวัตถุเป็นจำนวนมากสำหรับสิ่งที่คุณจะทิ้งทันที
ส่วนที่เหลือของคำตอบนี้จะอธิบายรายละเอียดเพิ่มเติมเล็กน้อยว่าคุณจะยืนยันได้อย่างไร
การเปรียบเทียบที่ดีขึ้น
คุณต้องการที่จะใช้กรอบเช่น jmhในการทำเบนช์มาร์กด้วยความรับผิดชอบบน JVM และส่วนที่รับผิดชอบก็ยากแม้ว่าการตั้งค่า 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
รุ่นนี้มีการจัดสรรมากกว่าสามเท่า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
}
โปรดทราบว่าแตกต่างจากรุ่นที่ได้รับการปรับปรุงในหนึ่งในคำตอบอื่น ๆ สิ่งนี้ใช้while
แทนคำว่าfor
ตั้งแต่for
จะยังคง desugar ในการดำเนินการรวบรวม Scala เราสามารถเปรียบเทียบการใช้งานนี้ ( 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
นี่คือสิ่งที่ฉันคาดหวังเนื่องจากไม่มีอะไรแพงในการเรียกใช้ฟังก์ชันและเนื่องจากการเข้าถึงองค์ประกอบอาเรย์โดยดัชนีมีราคาถูกมาก