अन्य उत्तरों में से कोई भी गति में अंतर के प्राथमिक कारण का उल्लेख नहीं करता है, जो यह है कि 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
संस्करण को तीन गुना अधिक आवंटित किया जा रहा है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
अभी भी स्कैला संग्रह के संचालन में वांछित होगा। हम इस कार्यान्वयन की तुलना कर सकते हैं ( 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
यह वही है, जिसकी मुझे उम्मीद है, क्योंकि किसी फ़ंक्शन को कॉल करने के बारे में कुछ भी महंगा नहीं है, और क्योंकि सूचकांक द्वारा सरणी तत्वों तक पहुंचना बहुत सस्ता है।