স্কালায় জিপের চেয়ে দ্রুত জিপ করা হয় কেন?


38

একটি সংকলনে উপাদান-ভিত্তিক ক্রিয়াকলাপ সম্পাদনের জন্য আমি কিছু স্কাল কোড লিখেছি। এখানে আমি একই পদ্ধতি সম্পাদনকারী দুটি পদ্ধতি সংজ্ঞায়িত করেছি। একটি পদ্ধতি ব্যবহার করে zipএবং অন্যটি ব্যবহার করে zipped

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)

গতির দিক দিয়ে এই দুটি পদ্ধতির তুলনা করতে, আমি নিম্নলিখিত কোডটি লিখেছি:

def fun (arr : Array[Double] , arr1 : Array[Double] , f :(Array[Double],Array[Double]) => Array[Double] , itr : Int) ={
  val t0 = System.nanoTime()
  for (i <- 1 to itr) {
       f(arr,arr1)
       }
  val t1 = System.nanoTime()
  println("Total Time Consumed:" + ((t1 - t0).toDouble / 1000000000).toDouble + "Seconds")
}

আমি funপদ্ধতিটি কল এবং পাস ESএবং ES1নীচের হিসাবে:

fun(Array.fill(10000)(math.random), Array.fill(10000)(math.random), ES , 100000)
fun(Array.fill(10000)(math.random), Array.fill(10000)(math.random), ES1, 100000)

ফলাফল দেখান যে পদ্ধতি নামে ES1যে ব্যবহারসমূহ zippedপদ্ধতি চেয়ে দ্রুত ESযে ব্যবহারসমূহ zip। এই পর্যবেক্ষণগুলির ভিত্তিতে আমার দুটি প্রশ্ন রয়েছে।

এর zippedচেয়ে দ্রুত কেন zip?

স্কালার কোনও সংগ্রহে উপাদান-ভিত্তিক ক্রিয়াকলাপ করার কি আরও দ্রুত উপায় আছে?



8
কারণ দ্বিতীয়বার 'মজা' করা হচ্ছে দেখে জেআইটি আরও আক্রমণাত্মকভাবে আরও অনুকূল করার সিদ্ধান্ত নিয়েছে। অথবা ইসি চলাকালীন জিসি কিছু পরিষ্কার করার সিদ্ধান্ত নিয়েছে। অথবা আপনার অপারেটিং সিস্টেমটি সিদ্ধান্ত নিয়েছে যে আপনার ES পরীক্ষা চলাকালীন এটি করার আরও ভাল জিনিস রয়েছে। যে কোনও কিছু হতে পারে, এই মাইক্রোবেনমার্কটি কেবল চূড়ান্ত নয়।
আন্দ্রে টিউকিন

1
আপনার মেশিনে ফলাফল কি? কত দ্রুত?
পিয়ুষ কুশওয়াহা

একই জনসংখ্যার আকার এবং কনফিগারেশনের জন্য
জিপটি

3
আপনার ফলাফল অর্থহীন। জেএমএইচ ব্যবহার করুন যদি আপনাকে অবশ্যই মাইক্রো-বেঞ্চমার্কগুলি করতে হয়।
অরেঞ্জডগ

উত্তর:


17

আপনার দ্বিতীয় প্রশ্নের উত্তর দিতে:

স্কালায় কোনও সংগ্রহে উপাদান অনুসারে অপারেশন করার কি আরও দ্রুত কোন উপায় আছে?

দুঃখজনক সত্যটি হ'ল এটি সংক্ষিপ্ততা, উন্নত উত্পাদনশীলতা এবং বাগের প্রতিরোধের দক্ষতা থাকা সত্ত্বেও যেগুলি কার্যকরী ভাষাগুলি সর্বাধিক পারফরম্যান্ট নয় - সংগ্রহের বিরুদ্ধে নিখরচায় কার্যকর করার জন্য কোনও প্রজেকশন নির্ধারণ করতে উচ্চতর অর্ডার ফাংশনগুলি ব্যবহার করে এবং আপনার আঁট লুপটি এটি হাইলাইট করে। অন্যরা যেমন উল্লেখ করেছে, মধ্যবর্তী এবং চূড়ান্ত ফলাফলের জন্য অতিরিক্ত স্টোরেজ বরাদ্দেরও ওভারহেড থাকবে।

যদি পারফরম্যান্সটি সমালোচিত হয় তবে যদিও এটি সর্বজনীন নয়, আপনার মতো ক্ষেত্রে আপনি মেমরির ব্যবহার এবং ফাংশন কলগুলি দূর করার জন্য আরও সরাসরি নিয়ন্ত্রণ ফিরে পেতে স্কালার ক্রিয়াকলাপগুলি অপরিহার্য সমতুল্য হিসাবে ফিরিয়ে আনতে পারেন।

আপনার নির্দিষ্ট উদাহরণে, zippedসঠিক আকারের একটি স্থির, পরিবর্তনীয় অ্যারে প্রাক বরাদ্দ করে (অর্থ সংগ্রহের কোনও উপাদান যখন শেষ হয়ে যায় তখন জিপ থামবে) এবং তারপরে যথাযথ সূচকে উপাদানগুলি একসাথে যোগ করে (অ্যাক্সেসের পরে অর্ডিনাল সূচক দ্বারা অ্যারে উপাদানগুলি একটি খুব দ্রুত অপারেশন)।

ES3আপনার পরীক্ষার স্যুটে তৃতীয় ফাংশন যুক্ত করা হচ্ছে :

def ES3(arr :Array[Double], arr1 :Array[Double]) :Array[Double] = {
   val minSize = math.min(arr.length, arr1.length)
   val array = Array.ofDim[Double](minSize)
   for (i <- 0 to minSize - 1) {
     array(i) = arr(i) + arr1(i)
   }
  array
}

আমার আই 7-তে আমি নিম্নলিখিত প্রতিক্রিয়া বার পেয়েছি:

OP ES Total Time Consumed:23.3747857Seconds
OP ES1 Total Time Consumed:11.7506995Seconds
--
ES3 Total Time Consumed:1.0255231Seconds

এর চেয়েও বেশি জঘন্য কাজ দুটি অ্যারের সংক্ষিপ্তস্থানের সরাসরি জায়গায় স্থানান্তর করা হবে যা স্পষ্টতই কোনও অ্যারের সামগ্রীতে দূষিত হবে এবং কেবল তখনই সম্পন্ন হবে যদি আবার মূল অ্যারের প্রয়োজন না হয়:

def ES4(arr :Array[Double], arr1 :Array[Double]) :Array[Double] = {
   val minSize = math.min(arr.length, arr1.length)
   val array = if (arr.length < arr1.length) arr else arr1
   for (i <- 0 to minSize - 1) {
      array(i) = arr(i) + arr1(i)
   }
  array
}

Total Time Consumed:0.3542098Seconds

তবে স্পষ্টতই অ্যারে উপাদানগুলির সরাসরি রূপান্তর স্কালার অনুভূতিতে নয়।


2
উপরের আমার কোডে সমান্তরালে কিছুই নেই। যদিও এই নির্দিষ্ট সমস্যাটি সমান্তরালযোগ্য (যেহেতু একাধিক থ্রেড অ্যারের বিভিন্ন বিভাগে কাজ করতে পারে), কেবলমাত্র 10 কে উপাদানগুলিতে এ জাতীয় সরল ক্রিয়াকলাপের তেমন কোনও বক্তব্য থাকবে না - নতুন থ্রেড তৈরি এবং সিঙ্ক্রোনাইজ করার ওভারহেড কোনও লাভের চেয়েও বেশি হতে পারে । সত্যি কথা বলতে, যদি আপনার এই স্তরের পারফরম্যান্স অপটিমাইজেশন প্রয়োজন হয় তবে আপনি সম্ভবত মরিচা, গো বা সি এই ধরণের অ্যালগরিদমগুলি লেখার চেয়ে ভাল
স্টুয়ার্টলসি

3
Array.tabulate(minSize)(i => arr(i) + arr1(i))আপনার অ্যারে তৈরি করতে এটি আরও স্কেলার মতো এবং দ্রুত ব্যবহারযোগ্য হবে
সর্বवेश কুমার সিংহ

1
@ সার্वेशকুমারসিংহ এটি অনেক ধীর। প্রায় 9 সেকেন্ড সময় নেয়
ব্যবহারকারী 12140540

1
Array.tabulateহয় zipবা zippedএখানে (এবং আমার মানদণ্ডে) এর চেয়ে অনেক দ্রুত হওয়া উচিত ।
ট্র্যাভিস ব্রাউন

1
@ স্টুয়ার্টএলসি "উচ্চতর অর্ডার ফাংশনটি যদি কোনওভাবে আবদ্ধ এবং ইনলাইন করা থাকে তবে পারফরম্যান্সই সমান হবে" " এটি সত্যই সঠিক নয়। এমনকি আপনার forউচ্চতর অর্ডার ফাংশন কল ( foreach) এর জন্য ডিজাইন করা হয় । উভয় ক্ষেত্রে ল্যাম্বদা কেবল একবার ইনস্ট্যান্ট করা হবে।
ট্র্যাভিস ব্রাউন

50

অন্যান্য উত্তরগুলির মধ্যে কোনওটিতেই গতির পার্থক্যের প্রাথমিক কারণ উল্লেখ করা হয়নি, এটি হ'ল zippedসংস্করণ 10,000 টি দ্বিগুণ বরাদ্দ এড়িয়ে চলে। অন্যান্য উত্তর একটি দম্পতি হিসাবে না নোট, zipসংস্করণ যখন কোন মধ্যবর্তী অ্যারের জড়িত থাকে, zippedসংস্করণ না, কিন্তু 10,000 উপাদানের জন্য একটি অ্যারের বণ্টন কি করে নয় zipসংস্করণ এত খারাপ এটা 10,000 সংক্ষিপ্ত ছিলো tuples যে যে অ্যারে করা হচ্ছে। এগুলি JVM- তে অবজেক্ট দ্বারা প্রতিনিধিত্ব করা হয়, সুতরাং আপনি অবিলম্বে দূরে ফেলতে চলেছেন এমন জিনিসগুলির জন্য আপনি একগুচ্ছ অবজেক্ট বরাদ্দ করছেন।

এই উত্তরটির বাকী অংশটি আপনি কীভাবে এটি নিশ্চিত করতে পারবেন সে সম্পর্কে আরও কিছুটা বিশদে যায়।

ভাল বেঞ্চমার্কিং

আপনি সত্যিই jmh এর মতো একটি কাঠামো ব্যবহার করতে চান জেভিএম-তে জেভিএম-তে দায়িত্বপূর্ণভাবে কোনও ধরণের বেঞ্চমার্কিং করার জন্য এবং তারপরেও দায়িত্বশীলতার অংশটি শক্ত, যদিও জেএমএইচ সেটআপ খুব খারাপ নয় isn't আপনার যদি এর 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% বেশি থ্রুপুট পায় যা সম্ভবত আপনার পরিমাপের মতো কমবেশি একই।

বরাদ্দ পরিমাপ

আপনি এর সাথে জেএমএইচটি বরাদ্দগুলি পরিমাপ করতে বলতে পারেন -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এখনও Scala সংগ্রহ অভিযানের মধ্যে desugar হবে। আমরা এই বাস্তবায়ন ( 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 সংস্করণ আরো ভালো পঠনযোগ্য, আমি ব্যক্তিগতভাবে কোন অর্থে যা অনুজ্ঞাসূচক সংস্করণ "Scala আত্মা 'বা মনোভাব বিরুদ্ধে আছে মনে করি না, এবং আমি দ্বিধা করবে না সেগুলি নিজেই ব্যবহার করার জন্য।

ট্যাবলেট সহ

আপডেট: আমি 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

এটিই আমি প্রত্যাশা করছিলাম, যেহেতু কোনও ফাংশন কল করার মতো অন্তর্নিহিত ব্যয়বহুল কিছুই নেই এবং কারণ সূচকে অ্যারে উপাদানগুলি অ্যাক্সেস করা খুব সস্তা cheap


8

বিবেচনা lazyZip

(as lazyZip bs) map { case (a, b) => a + b }

পরিবর্তে zip

(as zip bs) map { case (a, b) => a + b }

স্কেলা 2.13 এর পক্ষে যুক্ত lazyZip হয়েছে.zipped

একসাথে .zipদর্শনগুলির সাথে, এটি প্রতিস্থাপন করে .zipped(এখন হ্রাস করা হয়)। ( স্কেলা / সংগ্রহ-স্ট্রোম্যান # 223 )

zipped(এবং অত: পর lazyZip) দ্রুততর চেয়ে zipযেমন দ্বারা ব্যাখ্যা, কারণ টিম এবং মাইক অ্যালেন , zipঅনুসৃত দ্বারা mapকষাকষি কারণে দুটি পৃথক রূপান্তরের স্থাপিত হবে, যখন zippedঅনুসৃত দ্বারা mapআলস্য কারণে এক বারেই মৃত্যুদন্ড কার্যকর একটি একক রূপান্তর হয়ে যাবে।

zippedদেয় Tuple2Zipped, এবং বিশ্লেষণ করে Tuple2Zipped.map,

class Tuple2Zipped[...](val colls: (It1, It2)) extends ... {
  private def coll1 = colls._1
  private def coll2 = colls._2

  def map[...](f: (El1, El2) => B)(...) = {
    val b = bf.newBuilder(coll1)
    ...
    val elems1 = coll1.iterator
    val elems2 = coll2.iterator

    while (elems1.hasNext && elems2.hasNext) {
      b += f(elems1.next(), elems2.next())
    }

    b.result()
  }

আমরা দুটি সংগ্রহ দেখতে পাই coll1এবং coll2পুনরাবৃত্ত হয় এবং প্রতিটি পুনরাবৃত্তিতে ফাংশনটি fপাস mapহয়ে যায় এবং সেই পথে প্রয়োগ করা হয়

b += f(elems1.next(), elems2.next())

মধ্যস্থতাকারী কাঠামো বরাদ্দ এবং রূপান্তর না করেই।


ট্র্যাভিসের 'বেঞ্চমার্কিং পদ্ধতিটি প্রয়োগ করা, এখানে নতুন lazyZipএবং হ্রাসপ্রাপ্ত zippedযেখানে একটির মধ্যে একটি তুলনা করা হয়েছে

@State(Scope.Benchmark)
@BenchmarkMode(Array(Mode.Throughput))
class ZippedBench {
  import scala.collection.mutable._
  val as = ArraySeq.fill(10000)(math.random)
  val bs = ArraySeq.fill(10000)(math.random)

  def lazyZip(as: ArraySeq[Double], bs: ArraySeq[Double]): ArraySeq[Double] =
    as.lazyZip(bs).map{ case (a, b) => a + b }

  def zipped(as: ArraySeq[Double], bs: ArraySeq[Double]): ArraySeq[Double] =
    (as, bs).zipped.map { case (a, b) => a + b }

  def lazyZipJavaArray(as: Array[Double], bs: Array[Double]): Array[Double] =
    as.lazyZip(bs).map{ case (a, b) => a + b }

  @Benchmark def withZipped: ArraySeq[Double] = zipped(as, bs)
  @Benchmark def withLazyZip: ArraySeq[Double] = lazyZip(as, bs)
  @Benchmark def withLazyZipJavaArray: ArraySeq[Double] = lazyZipJavaArray(as.toArray, bs.toArray)
}

দেয়

[info] Benchmark                          Mode  Cnt      Score      Error  Units
[info] ZippedBench.withZipped            thrpt   20  20197.344 ± 1282.414  ops/s
[info] ZippedBench.withLazyZip           thrpt   20  25468.458 ± 2720.860  ops/s
[info] ZippedBench.withLazyZipJavaArray  thrpt   20   5215.621 ±  233.270  ops/s

lazyZipএকটু বেশী ভালো সঞ্চালন বলে মনে হয় zippedউপর ArraySeq। মজার ব্যাপার হচ্ছে, যখন ব্যবহার উল্লেখযোগ্যভাবে কর্মক্ষমতা হ্রাস লক্ষ্য lazyZipউপর Array


অলসজিপ স্কেলা 2.13.1 এ উপলব্ধ। বর্তমানে আমি স্কেলা 2.11.8 ব্যবহার করছি
ব্যবহারকারী 12140540

5

আপনার জেআইটি সংকলনের কারণে পারফরম্যান্স পরিমাপের বিষয়ে সর্বদা সতর্ক হওয়া উচিত, তবে সম্ভাব্য কারণ zippedহ'ল অলস এবং কল Arrayচলাকালীন মূল ভোলগুলি থেকে উপাদানগুলি বের করে map, সেখানে zipএকটি নতুন Arrayঅবজেক্ট তৈরি করা হয় এবং তারপরে নতুন বস্তুকে কল mapকরে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.