কেন Scala এবং স্ফুলিঙ্গ মত অবকাঠামো ও বুকফাটা উভয় আছে reduce
এবং foldLeft
? আমি তখন এর মধ্যে পার্থক্য কি reduce
এবং fold
?
কেন Scala এবং স্ফুলিঙ্গ মত অবকাঠামো ও বুকফাটা উভয় আছে reduce
এবং foldLeft
? আমি তখন এর মধ্যে পার্থক্য কি reduce
এবং fold
?
উত্তর:
একটি বড় বড় পার্থক্য, যা এই বিষয়ে স্পষ্টভাবে সম্পর্কিত অন্য কোনও স্ট্যাকওভারফ্লো উত্তরে উল্লেখ করা হয়নি, তা reduce
হল একটি ক্রমবর্ধমান মনোয়েড দেওয়া উচিত , অর্থাত্ একটি ক্রিয়াকলাপ যা ভ্রমণ এবং সহযোগী উভয়ই হয়। এর অর্থ অপারেশনটি সমান্তরাল হতে পারে।
বিগ ডেটা / এমপিপি / ডিস্ট্রিবিউটেড কম্পিউটিং এবং reduce
এমনকি কেন বিদ্যমান রয়েছে তার পুরো কারণেই এই পার্থক্যটি অত্যন্ত গুরুত্বপূর্ণ । সংগ্রহটি কাটা যাবে এবং reduce
প্রতিটি খণ্ডে এটি পরিচালনা করতে পারে , তারপরে reduce
প্রতিটি খণ্ডের ফলাফলগুলি পরিচালনা করতে পারে - বাস্তবে খণ্ডনের স্তরটি এক স্তর গভীর থেকে থামতে হবে না। আমরা প্রতিটি অংশ খুব কাটা যেতে পারে। এই কারণেই একটি তালিকায় সংখ্যার যোগফলকে O (লগ এন) দেওয়া হয় যদি অসীম সংখ্যক সিপিইউ দেওয়া হয়।
আপনি যদি স্বাক্ষরগুলির দিকে তাকান তবে reduce
অস্তিত্বের কোনও কারণ নেই কারণ আপনি এটি দিয়ে reduce
যা কিছু করতে পারেন তা অর্জন করতে পারেন foldLeft
। এর কার্যকারিতা এর কার্যকারিতার foldLeft
চেয়ে বৃহত্তর reduce
।
তবে আপনি একটি সমান্তরাল করতে পারবেন না foldLeft
, সুতরাং এটির রানটাইমটি সর্বদা ও (এন) হয় (আপনি যদি কোনও পরিবহিত মনোয়েড খাওয়ান)। এর কারণ হল এটা অধিকৃত অপারেশন হয় না একটি বিনিময় monoid এবং তাই ক্রমযোজিত মান অনুক্রমিক aggregations একটি সিরিজ দ্বারা নির্ণিত করা হবে না।
foldLeft
চলাচল বা সাহসীতা গ্রহণ করে না। এটি সাহসিকতা যা সংগ্রহটি কাটা করার ক্ষমতা দেয় এবং এটি ক্রমবর্ধমানতা যা কমুলেটকে সহজ করে তোলে কারণ আদেশ গুরুত্বপূর্ণ নয় (তাই প্রতিটি খণ্ড থেকে ফলাফলের প্রতিটি ফলাফলকে একত্রিত করার জন্য কোন আদেশ তা বিবেচনা করে না)। সমান্তরালকরণের জন্য কঠোরভাবে যোগাযোগের প্রয়োজন হয় না, উদাহরণস্বরূপ বিতরণ করা অ্যালগরিদমগুলি বিতর্ক করা এটি যুক্তিটিকে আরও সহজ করে তোলে কারণ আপনার খণ্ডগুলিকে অর্ডার দেওয়ার দরকার নেই।
আপনার যদি স্পার্ক ডকুমেন্টেশনের দিকে নজর থাকে তবে reduce
সুনির্দিষ্টভাবে বলে ... "... পরিবর্তনশীল এবং সহযোগী বাইনারি অপারেটর"
http://spark.apache.org/docs/1.0.0/api/scala/index.html#org.apache.spark.rdd.RDD
এখানে এমন প্রমাণ রয়েছে যা reduce
কেবল একটি বিশেষ ক্ষেত্রে নয়foldLeft
scala> val intParList: ParSeq[Int] = (1 to 100000).map(_ => scala.util.Random.nextInt()).par
scala> timeMany(1000, intParList.reduce(_ + _))
Took 462.395867 milli seconds
scala> timeMany(1000, intParList.foldLeft(0)(_ + _))
Took 2589.363031 milli seconds
এখন এখান থেকে এটি এফপি / গাণিতিক শিকাগুলির আরও কাছাকাছি যায় এবং ব্যাখ্যা করার জন্য একটু কৌশলযুক্ত হয়। হ্রাস হ'ল ম্যাপ্রেডিউস দৃষ্টান্তের অংশ হিসাবে আনুষ্ঠানিকভাবে সংজ্ঞায়িত করা হয়েছে, যা অর্ডারলেস সংগ্রহের (মাল্টিসেটস) কাজ করে, ভাঁজটি পুনরাবৃত্তি হিসাবে (সংঘবদ্ধতা দেখুন) পদে সংজ্ঞায়িত হয় এবং এইভাবে সংগ্রহের কাঠামো / ক্রম অনুমান করে।
fold
স্ক্যালডিংয়ে কোনও পদ্ধতি নেই কারণ (কঠোর) মানচিত্র হ্রাস প্রোগ্রামিং মডেলের অধীনে আমরা সংজ্ঞা দিতে fold
পারি না কারণ খণ্ডগুলির কোনও অর্ডার নেই এবং fold
কেবল যোগাযোগের প্রয়োজন নয়, কেবল মিশ্রতা প্রয়োজন।
সরলভাবে বলুন, reduce
সংক্রমণের অর্ডার ব্যতীত কাজ করে, fold
সংক্রমণের অর্ডার প্রয়োজন এবং এটি সেই সংকলনের ক্রম যা শূন্য মানের নয় যা তাদের আলাদা করে শূন্য মানের অস্তিত্বের প্রয়োজন। কঠোরভাবে বলতে reduce
উচিত একটি খালি সংগ্রহের কাজ করে, কারণ একটি অবাধ মান গ্রহণ করে অনুমিত দ্বারা তার শূন্য মান করতে পারেন x
এবং তারপর সমাধানে x op y = x
, কিন্তু যে কোনো অ-বিনিময় অপারেশন সঙ্গে কাজ করে না যেমন আছে বাম এবং ডান শূন্য মান স্বতন্ত্র বিদ্যমান পারেন না (অর্থাত্ x op y != y op x
) অবশ্যই স্কেলা এই শূন্যের মানটি কী তা নিয়ে কাজ করতে বিরক্ত করে না কারণ এর জন্য কিছু গণিত করা প্রয়োজন (যা সম্ভবত অসম্পূর্ণযোগ্য), তাই কেবল একটি ব্যতিক্রম ছোঁড়ে।
দেখে মনে হচ্ছে (ব্যুৎপত্তি ক্ষেত্রে প্রায়শই দেখা যায়) এই আসল গাণিতিক অর্থটি হারিয়ে গেছে, কারণ প্রোগ্রামিংয়ের একমাত্র সুস্পষ্ট পার্থক্য হ'ল স্বাক্ষর। ফলাফলটি মানচিত্রের মূল অর্থ সংরক্ষণের পরিবর্তে এর reduce
প্রতিশব্দ হয়ে উঠেছে fold
। এখন এই পদগুলি প্রায়শই আন্তঃবিস্মরণীয়ভাবে ব্যবহৃত হয় এবং বেশিরভাগ বাস্তবায়নে (খালি সংগ্রহ উপেক্ষা করে) একই আচরণ করে। অদ্ভুততা স্পার্কের মতো অদ্ভুততার দ্বারা আরও বেড়ে যায়, যা আমরা এখন সম্বোধন করব।
সুতরাং স্পার্ক করে একটি আছে fold
, কিন্তু ক্রমে সাব ফলাফল (প্রতিটি পার্টিশন জন্য এক) (লেখার সময়) একত্রিত করা হয় একই আদেশ, যা কর্ম সম্পন্ন - এবং এইভাবে অ নির্ণায়ক। ইশারা যে জন্য @CafeFeed ধন্যবাদ fold
ব্যবহারসমূহ runJob
, যা কোড মাধ্যমে পড়ার পর আমি বুঝতে পারি যে এটি নন-নির্ণায়ক নয়। আরও বিভ্রান্তি স্পার্ক একটি treeReduce
কিন্তু না থাকার দ্বারা তৈরি করা হয় treeFold
।
সেখানে মধ্যে একটি পার্থক্য আছে reduce
এবং fold
এমনকি যখন খালি নয় এমন সিকোয়েন্স প্রয়োগ করা হয়েছিল। পূর্ববর্তীটিকে নির্বিচারে আদেশের ( http://theory.stanford.edu/~sergei/papers/soda10-mrc.pdf ) সংগ্রহের উপর মানচিত্রের প্রোগ্রামিং দৃষ্টান্তের অংশ হিসাবে সংজ্ঞায়িত করা হয়েছে এবং অপারেটররা সত্ত্বেও বহিরাগত হয়ে উঠছেন বলে ধরে নেওয়া উচিত সংযোজনীয় ফলাফল দিতে সংঘবদ্ধ। পরেরটি ক্যাটোমর্ফিজমের পরিভাষায় সংজ্ঞায়িত হয় এবং প্রয়োজন হয় যে সংগ্রহগুলি ক্রমের একটি ধারণা থাকতে পারে (বা সংযুক্ত তালিকার মতো পুনরাবৃত্তভাবে সংজ্ঞায়িত করা হয়), সুতরাং চলমান অপারেটরগুলির প্রয়োজন হয় না।
বাস্তবে প্রোগ্রামিংয়ের তুলনাহীন প্রকৃতির কারণে reduce
এবং fold
একইভাবে আচরণ করার ঝোঁক হয়, হয় সঠিকভাবে (স্কালার মতো) বা ভুলভাবে (স্পার্কের মতো)।
আমার মতামতটি হল fold
স্পার্কে শব্দটি সম্পূর্ণরূপে বাদ দেওয়া হলে বিভ্রান্তি এড়ানো হবে । কমপক্ষে স্পার্কের নথিতে একটি নোট রয়েছে:
এটি স্কালার মতো কার্যকরী ভাষায় বিতরণবিহীন সংগ্রহের জন্য প্রয়োগ করা ভাঁজ অপারেশন থেকে কিছুটা আলাদা আচরণ করে।
foldLeft
রয়েছে Left
তার নামে এবং কেন সেখানে একটি পদ্ধতি বলা হয় fold
।
.par
, তাই (List(1000000.0) ::: List.tabulate(100)(_ + 0.001)).par.reduce(_ / _)
প্রতিবারই আমি বিভিন্ন ফলাফল পাই।
reallyFold
যদিও: যেমন rdd.mapPartitions(it => Iterator(it.fold(zero)(f)))).collect().fold(zero)(f)
, যাতায়াত করতে এফের দরকার হবে না।
আমি যদি ভুল না হয়ে থাকি, যদিও স্ফুলিপি এপিআই এর প্রয়োজন হয় না, তবে ভাঁজটির জন্য এফটি পরিবহনযোগ্য হওয়া আবশ্যক। কারণ যে ক্রমে পার্টিশনগুলি একত্রিত করা হবে তা নিশ্চিত নয়। উদাহরণস্বরূপ, নিম্নলিখিত কোডে কেবল প্রথম প্রিন্ট আউট বাছাই করা হয়:
import org.apache.spark.{SparkConf, SparkContext}
object FoldExample extends App{
val conf = new SparkConf()
.setMaster("local[*]")
.setAppName("Simple Application")
implicit val sc = new SparkContext(conf)
val range = ('a' to 'z').map(_.toString)
val rdd = sc.parallelize(range)
println(range.reduce(_ + _))
println(rdd.reduce(_ + _))
println(rdd.fold("")(_ + _))
}
প্রিন্ট আউট:
abcdefghijklmnopqrstuvwxyz
abcghituvjklmwxyzqrsdefnop
Defghinopjklmqrstuvabcwxyz
sc.makeRDD(0 to 9, 2).mapPartitions(it => { java.lang.Thread.sleep(new java.util.Random().nextInt(1000)); it } ).map(_.toString).fold("")(_ + _)
বেশ কয়েকবার 2+ কোরের সাথে চালনা করেন তবে আমার মনে হয় আপনি এটি দেখতে পারবেন এলোমেলো (পার্টিশন-ভিত্তিক) ক্রম। আমি আমার উত্তর অনুসারে আপডেট করেছি।
fold
অ্যাপাচি স্পার্কে fold
বিতরণ না করা সংগ্রহের মতো নয়। প্রকৃতপক্ষে ডিটারমিনিটিক ফলাফল আনতে এটির জন্য চলমান কার্য প্রয়োজন :
এটি স্কালার মতো কার্যকরী ভাষায় বিতরণবিহীন সংগ্রহের জন্য প্রয়োগ করা ভাঁজ অপারেশন থেকে কিছুটা আলাদা আচরণ করে। এই ভাঁজ অপারেশনটি পৃথক পৃথকভাবে পার্টিশনের ক্ষেত্রে প্রয়োগ করা যেতে পারে এবং তারপরে কোনও ফলাফলকে কিছু সংজ্ঞায়িত ক্রমে ক্রমান্বয়ে প্রতিটি উপাদানটিতে ভাঁজটি প্রয়োগ না করে চূড়ান্ত ফলাফলের মধ্যে ফলাফলগুলি ফোল্ড করে। যে ক্রিয়াকলাপগুলি পরিবহণযোগ্য নয়, তাদের জন্য ফলাফল একটি বিতরণকৃত সংগ্রহের জন্য প্রয়োগ করা ভাঁজ থেকে পৃথক হতে পারে।
এই দেখানো হয়েছে দ্বারা মীশায়েল Rosenthal এবং দ্বারা প্রস্তাবিত Make42 মধ্যে তার মন্তব্য ।
এটি প্রস্তাবিত হয়েছে যে পর্যবেক্ষণ করা আচরণ সম্পর্কিত হয় HashPartitioner
যখন বাস্তবে পরিবর্তন parallelize
হয় না এবং ব্যবহার হয় না HashPartitioner
।
import org.apache.spark.sql.SparkSession
/* Note: standalone (non-local) mode */
val master = "spark://...:7077"
val spark = SparkSession.builder.master(master).getOrCreate()
/* Note: deterministic order */
val rdd = sc.parallelize(Seq("a", "b", "c", "d"), 4).sortBy(identity[String])
require(rdd.collect.sliding(2).forall { case Array(x, y) => x < y })
/* Note: all posible permutations */
require(Seq.fill(1000)(rdd.fold("")(_ + _)).toSet.size == 24)
ব্যাখ্যা:
def fold(zeroValue: T)(op: (T, T) => T): T = withScope {
var jobResult: T
val cleanOp: (T, T) => T
val foldPartition = Iterator[T] => T
val mergeResult: (Int, T) => Unit
sc.runJob(this, foldPartition, mergeResult)
jobResult
}
আরডিডির কাঠামোরreduce
মতোই :
def reduce(f: (T, T) => T): T = withScope {
val cleanF: (T, T) => T
val reducePartition: Iterator[T] => Option[T]
var jobResult: Option[T]
val mergeResult = (Int, Option[T]) => Unit
sc.runJob(this, reducePartition, mergeResult)
jobResult.getOrElse(throw new UnsupportedOperationException("empty collection"))
}
যেখানে runJob
পার্টিশন ক্রমকে অগ্রাহ্য করার সাথে সঞ্চালিত হয় এবং ফলাফলের পরিবর্তিত ক্রিয়াকলাপ প্রয়োজন।
foldPartition
এবং reducePartition
প্রক্রিয়াকরণ আদেশের শর্ত এবং কার্যকরভাবে (উত্তরাধিকার এবং প্রতিনিধিদলের দ্বারা) দ্বারা বাস্তবায়িত মধ্যে হয় সমতুল্য reduceLeft
এবং foldLeft
উপর TraversableOnce
।
উপসংহার: fold
আরডিডি-তে খণ্ডের ক্রমের উপর নির্ভর করতে পারে না এবং প্রয়োজন যোগাযোগ এবং সাহচর্য ।
fold
এটি RDD
সত্যই সত্যই ঠিক তেমন একই reduce
তবে এটি মূল গাণিতিক পার্থক্যের প্রতি সম্মান দেয় না (আমি আরও উত্তর পরিষ্কার করার জন্য আমার উত্তর আপডেট করেছি)। যদিও আমি একমত নই যে আমাদের সত্যিকারের চলাচলের প্রয়োজন, যদি তাদের পার্টিশনার যা-কিছু করুক না কেন সে বিষয়ে আত্মবিশ্বাসী, তবে এটি অর্ডার সংরক্ষণ করছে।
runJob
কোডটি পড়ে আমি দেখতে পেলাম যে কোনও কাজ শেষ হওয়ার পরে এটি অবশ্যই সংমিশ্রণটি করে, পার্টিশনের ক্রম নয়। এটি এই মূল বিবরণ যা সমস্ত কিছু জায়গায় পড়ে। আমি আমার উত্তরটি আবার সম্পাদনা করেছি এবং এভাবে আপনি যে ভুলটি উল্লেখ করেছেন তা সংশোধন করেছি । আমরা যেহেতু এখন চুক্তিতে রয়েছি আপনি দয়া করে আপনার অনুগ্রহটি সরিয়ে ফেলতে পারেন?
স্কালডিংয়ের জন্য আর একটি পার্থক্য হাদুপে কম্বিনার ব্যবহার।
আপনার ক্রিয়াকলাপটি পরিবর্তনীয় একঘেয়েমি বলে মনে করুন, হ্রাস সহ এটি হ্রাসকারীদের সাথে সমস্ত ডেটা বাছাই / সাজানোর পরিবর্তে মানচিত্রের পাশে প্রয়োগ করা হবে। সঙ্গে foldLeft এই ঘটনা না।
pipe.groupBy('product) {
_.reduce('price -> 'total){ (sum: Double, price: Double) => sum + price }
// reduce is .mapReduceMap in disguise
}
pipe.groupBy('product) {
_.foldLeft('price -> 'total)(0.0){ (sum: Double, price: Double) => sum + price }
}
আপনার ক্রিয়াকলাপকে স্কালডিং-এ monoid হিসাবে সংজ্ঞায়িত করা সর্বদা ভাল অনুশীলন।