कोड पर चर्चा करने वाला मूल उत्तर नीचे पाया जा सकता है।
सबसे पहले, आपको विभिन्न प्रकार के एपीआई के बीच अंतर करना होगा, प्रत्येक को अपने स्वयं के प्रदर्शन के विचारों के साथ।
RDD एपीआई
(जेवीएम आधारित ऑर्केस्ट्रेशन के साथ शुद्ध पायथन संरचनाएं)
यह वह घटक है जो पायथन कोड के प्रदर्शन और PySpark कार्यान्वयन के विवरण से सबसे अधिक प्रभावित होगा। जबकि पायथन प्रदर्शन में समस्या होने की संभावना नहीं है, कम से कम कुछ कारकों पर आपको विचार करना होगा:
- जेवीएम संचार का ओवरहेड। व्यावहारिक रूप से पायथन निष्पादक को आने वाले सभी डेटा को एक सॉकेट और एक जेवीएम कार्यकर्ता के माध्यम से पारित करना पड़ता है। हालांकि यह एक अपेक्षाकृत कुशल स्थानीय संचार है लेकिन यह अभी भी मुक्त नहीं है।
प्रक्रिया-आधारित निष्पादक (पायथन) बनाम धागा आधारित (एकल JVM कई सूत्र) निष्पादक (स्काला)। प्रत्येक पायथन निष्पादक अपनी प्रक्रिया में चलता है। एक साइड इफेक्ट के रूप में, यह अपने जेवीएम समकक्ष की तुलना में मजबूत अलगाव और निष्पादक जीवनचक्र पर कुछ नियंत्रण प्रदान करता है लेकिन संभावित रूप से महत्वपूर्ण मेमोरी मेमोरी का उपयोग करता है:
- दुभाषिया स्मृति पदचिह्न
- लोड किए गए पुस्तकालयों के पदचिह्न
- कम कुशल प्रसारण (प्रत्येक प्रक्रिया को प्रसारण की अपनी प्रति की आवश्यकता होती है)
पायथन कोड का प्रदर्शन। सामान्यतया स्काला पायथन की तुलना में तेज़ है, लेकिन यह कार्य पर अलग-अलग होगा। इसके अलावा आपके पास JITs जैसे Numba , C Extension ( Cython ) या Theano जैसी विशिष्ट लाइब्रेरी जैसे कई विकल्प हैं । अंत में, यदि आप ML / MLlib (या केवल NumPy स्टैक) का उपयोग नहीं करते हैं , तो PyPy को एक वैकल्पिक दुभाषिया के रूप में उपयोग करने पर विचार करें । SPARK-3094 देखें ।
- PySpark कॉन्फ़िगरेशन
spark.python.worker.reuse
विकल्प प्रदान करता है जिसका उपयोग प्रत्येक कार्य के लिए पायथन प्रक्रिया को चुनने और मौजूदा प्रक्रिया का पुन: उपयोग करने के बीच चयन करने के लिए किया जा सकता है। महंगा कचरा संग्रह से बचने के लिए बाद वाला विकल्प उपयोगी लगता है (यह व्यवस्थित परीक्षणों के परिणामस्वरूप अधिक प्रभाव है), जबकि महंगा प्रसारण और आयात के मामले में पूर्व वाला (डिफ़ॉल्ट) इष्टतम है।
- CPython में पहली पंक्ति कचरा संग्रहण विधि के रूप में उपयोग की जाने वाली संदर्भ गिनती, विशिष्ट स्पार्क वर्कलोड (स्ट्रीम-लाइक प्रोसेसिंग, नो रेफरेंस साइकल) के साथ बहुत अच्छी तरह से काम करती है और लंबे GC पॉज़ के जोखिम को कम करती है।
MLlib
(मिश्रित पायथन और जेवीएम निष्पादन)
कुछ अतिरिक्त मुद्दों के साथ पहले की तरह बुनियादी विचार बहुत अधिक हैं। जबकि MLlib के साथ उपयोग की जाने वाली बुनियादी संरचनाएं सादे पायथन आरडीडी ऑब्जेक्ट हैं, सभी एल्गोरिदम को सीधे स्काला का उपयोग करके निष्पादित किया जाता है।
इसका अर्थ है कि पाइथन ऑब्जेक्ट्स को स्काला ऑब्जेक्ट्स और अन्य तरीके से परिवर्तित करने की अतिरिक्त लागत, मेमोरी उपयोग में वृद्धि और कुछ अतिरिक्त सीमाएँ जिन्हें हम बाद में कवर करेंगे।
अब तक (स्पार्क 2.x), RDD- आधारित API एक रखरखाव मोड में है और इसे स्पार्क 3.0 में हटाया जाना निर्धारित है ।
DataFrame एपीआई और स्पार्क एमएल
(ड्राइवर के लिए सीमित पायथन कोड के साथ जेवीएम निष्पादन)
ये संभवतः मानक डेटा प्रोसेसिंग कार्यों के लिए सबसे अच्छा विकल्प हैं। चूंकि पायथन कोड ज्यादातर चालक पर उच्च-स्तरीय तार्किक संचालन तक सीमित है, इसलिए पायथन और स्काला के बीच कोई प्रदर्शन अंतर नहीं होना चाहिए।
एक एकल अपवाद पंक्ति-वार पायथन यूडीएफ का उपयोग है जो उनके स्काला समकक्षों की तुलना में काफी कम कुशल हैं। जबकि सुधार के लिए कुछ मौका है (स्पार्क 2.0.0 में पर्याप्त विकास हुआ है), सबसे बड़ी सीमा आंतरिक प्रतिनिधित्व (जेवीएम) और पायथन दुभाषिया के बीच पूर्ण राउंडट्रिप है। यदि संभव हो तो, आपको अंतर्निहित अभिव्यक्तियों की एक रचना का पक्ष लेना चाहिए ( उदाहरण । पायथन यूडीएफ व्यवहार को स्पार्क 2.0.0 में सुधार दिया गया है, लेकिन यह मूल निष्पादन की तुलना में अभी भी उप-रूपीय है।
यह भविष्य में सुधरे हुए वेक्टर UDFs (SPARK-21190 और आगे के विस्तार) की शुरूआत के साथ काफी सुधार हुआ है , जो कि जीरो-कॉपी डिसेरिएलाइजेशन के साथ कुशल डेटा एक्सचेंज के लिए एरो स्ट्रीमिंग का उपयोग करता है। अधिकांश अनुप्रयोगों के लिए उनके द्वितीयक ओवरहेड्स को अनदेखा किया जा सकता है।
इसके अलावा DataFrames
और बीच अनावश्यक डेटा से बचने के लिए सुनिश्चित करें RDDs
। पाइथन दुभाषिया से और इसके लिए डेटा ट्रांसफर का उल्लेख नहीं करने के लिए महंगे सीरियललाइज़ेशन और डीरियलाइज़ेशन की आवश्यकता होती है।
यह ध्यान देने योग्य है कि Py4J कॉल में उच्च विलंबता है। इसमें सरल कॉल शामिल हैं:
from pyspark.sql.functions import col
col("foo")
आमतौर पर, इससे कोई फर्क नहीं पड़ता (ओवरहेड स्थिर है और डेटा की मात्रा पर निर्भर नहीं करता है) लेकिन नरम वास्तविक समय के अनुप्रयोगों के मामले में, आप कैशिंग / जावा रैपर का पुन: उपयोग करने पर विचार कर सकते हैं।
ग्राफएक्स और स्पार्क डेटासेट्स
अभी तक (स्पार्क 1.6 2.1) न तो कोई PySpark API प्रदान करता है, इसलिए आप कह सकते हैं कि PySpark, Scala से असीम रूप से बदतर है।
GraphX
व्यवहार में, ग्राफएक्स का विकास लगभग पूरी तरह से बंद हो गया और परियोजना वर्तमान में संबंधित JIRA टिकटों के साथ रखरखाव मोड में है क्योंकि यह ठीक नहीं होगा । GraphFrames पुस्तकालय पायथन बाइंडिंग के साथ एक वैकल्पिक ग्राफ प्रसंस्करण पुस्तकालय प्रदान करता है।
डेटासेट
विशेष रूप से बोलना Datasets
, पायथन में वैधानिक रूप से टाइप किए जाने के लिए बहुत जगह नहीं है और यहां तक कि अगर वर्तमान स्काला कार्यान्वयन बहुत सरल है और जैसा प्रदर्शन लाभ प्रदान नहीं करता है DataFrame
।
स्ट्रीमिंग
मैंने अब तक जो देखा है, उससे मैं दृढ़ता से पायथन के ऊपर स्काला का उपयोग करने की सलाह दूंगा। यदि भविष्य में संरचित धाराओं के लिए समर्थन मिलता है तो यह भविष्य में बदल सकता है लेकिन अभी स्काला एपीआई अधिक मजबूत, व्यापक और कुशल प्रतीत होता है। मेरा अनुभव काफी सीमित है।
स्पार्क 2.x में संरचित स्ट्रीमिंग भाषाओं के बीच के अंतर को कम करने के लिए लगती है, लेकिन अभी यह अपने शुरुआती दिनों में है। फिर भी, RDD आधारित एपीआई को पहले से ही डाटब्रिक्स डॉक्यूमेंटेशन (2017-03-03 की अभिगमन तिथि) में "विरासत स्ट्रीमिंग" के रूप में संदर्भित किया गया है, इसलिए आगे एकीकरण के प्रयासों की उम्मीद करना उचित है।
गैर-प्रदर्शन विचार
फ़ीचर समता
नहीं सभी स्पार्क सुविधाओं PySpark एपीआई के माध्यम से उजागर कर रहे हैं। यह जांचना सुनिश्चित करें कि क्या आपको जिन भागों की आवश्यकता है वे पहले से ही लागू हैं और संभव सीमाओं को समझने की कोशिश करें।
यह विशेष रूप से महत्वपूर्ण है जब आप MLlib और इसी तरह के मिश्रित संदर्भों का उपयोग करते हैं ( एक कार्य से कॉलिंग जावा / स्काला फ़ंक्शन देखें )। पाइस्पार्क एपीआई के कुछ हिस्सों को उचित होने के लिए, जैसे mllib.linalg
, स्काला की तुलना में तरीकों का अधिक व्यापक सेट प्रदान करता है।
एपीआई डिजाइन
PySpark एपीआई बारीकी से अपने स्काला समकक्ष को दर्शाता है और जैसा कि बिल्कुल पाइथोनिक नहीं है। इसका मतलब है कि भाषाओं के बीच मानचित्र बनाना बहुत आसान है, लेकिन साथ ही, पायथन कोड को समझना काफी कठिन हो सकता है।
जटिल वास्तुकला
प्योरवार्क डेटा प्रवाह शुद्ध जेवीएम निष्पादन की तुलना में अपेक्षाकृत जटिल है। PySpark कार्यक्रमों या डीबग के बारे में तर्क करना बहुत कठिन है। इसके अलावा सामान्य रूप से स्काला और जेवीएम की कम से कम बुनियादी समझ बहुत जरूरी है।
चिंगारी 2.x और परे
Dataset
जमे हुए RDD एपीआई के साथ एपीआई की ओर बदलाव , अजगर उपयोगकर्ताओं के लिए अवसरों और चुनौतियों दोनों को लाता है। जबकि पाइथन में एपीआई के उच्च स्तर के हिस्सों को उजागर करना बहुत आसान है, और अधिक उन्नत सुविधाओं को सीधे उपयोग करने के लिए बहुत अधिक असंभव है ।
इसके अलावा देशी पायथन कार्य SQL दुनिया में दूसरे दर्जे के नागरिक बने हुए हैं। उम्मीद है कि यह भविष्य में Apache Arrow क्रमांकन ( वर्तमान प्रयासों लक्ष्य डेटाcollection
लेकिन UDF सर्ड एक दीर्घकालिक लक्ष्य है ) के साथ सुधार होगा ।
पायथन कोडबेस के आधार पर जोरदार परियोजनाओं के लिए, शुद्ध पायथन विकल्प (जैसे डस्क या रे ) एक दिलचस्प विकल्प हो सकता है।
यह एक बनाम दूसरे नहीं होना चाहिए
स्पार्क डेटाफ़्रेम (एसक्यूएल, डेटासेट) एपीआई पाइस्पार्क एप्लिकेशन में स्काला / जावा कोड को एकीकृत करने का एक सुंदर तरीका प्रदान करता है। आप DataFrames
डेटा को एक देशी जेवीएम कोड में उजागर करने और परिणामों को वापस पढ़ने के लिए उपयोग कर सकते हैं । मैंने कुछ और विकल्पों की व्याख्या की है और आप पायसपार्क के अंदर एक स्काला क्लास का उपयोग कैसे करें , पायथन-स्काला राउंडट्रिप का एक कार्यशील उदाहरण पा सकते हैं ।
उपयोगकर्ता परिभाषित प्रकारों को शुरू करके इसे और बढ़ाया जा सकता है (देखें कि स्पार्क एसक्यूएल में कस्टम प्रकार के लिए स्कीमा को कैसे परिभाषित किया जाए )।
प्रश्न में दिए गए कोड के साथ क्या गलत है
(डिसक्लेमर: पाइथोनिस्टा के दृष्टिकोण। सबसे अधिक संभावना है कि मैंने कुछ स्काला ट्रिक्स को याद किया है)
सबसे पहले, आपके कोड में एक हिस्सा है जो बिल्कुल भी समझ में नहीं आता है। यदि आपके पास पहले से ही (key, value)
जोड़े का उपयोग zipWithIndex
कर बनाया गया है या enumerate
स्ट्रिंग को बनाने के लिए क्या है, तो इसे ठीक से विभाजित करने के लिए? flatMap
पुनरावर्ती रूप से काम नहीं करता है, इसलिए आप केवल टुपल्स का उत्पादन कर सकते हैं और map
जो भी हो उसका अनुसरण करें ।
मुझे लगता है कि एक और हिस्सा समस्याग्रस्त है reduceByKey
। आम तौर पर बोलना, reduceByKey
उपयोगी है यदि समुच्चय फ़ंक्शन को लागू करने से डेटा की मात्रा कम हो सकती है जिसे फेरबदल करना पड़ता है। चूँकि आप बस तारों को समेटते हैं इसलिए यहाँ कुछ हासिल नहीं होता है। निम्न-स्तरीय सामान को अनदेखा करना, संदर्भों की संख्या की तरह, आपके द्वारा स्थानांतरित किए जाने वाले डेटा की मात्रा बिल्कुल उसी तरह है जैसे कि groupByKey
।
आम तौर पर मैं उस पर ध्यान नहीं देता, लेकिन जहां तक मैं बता सकता हूं कि यह आपके स्काला कोड में अड़चन है। जेवीएम पर तार जुड़ना एक महंगा ऑपरेशन है (उदाहरण के लिए देखें: क्या स्लैला में स्ट्रिंग का संघटन उतना ही महंगा है जितना जावा में? )। इसका मतलब यह है कि आपके कोड में _.reduceByKey((v1: String, v2: String) => v1 + ',' + v2)
समतुल्य कुछ इस तरह का input4.reduceByKey(valsConcat)
एक अच्छा विचार नहीं है।
आप से बचना चाहते हैं groupByKey
आप का उपयोग करने की कोशिश कर सकते हैं aggregateByKey
के साथ StringBuilder
। इसके साथ कुछ ऐसा करना चाहिए:
rdd.aggregateByKey(new StringBuilder)(
(acc, e) => {
if(!acc.isEmpty) acc.append(",").append(e)
else acc.append(e)
},
(acc1, acc2) => {
if(acc1.isEmpty | acc2.isEmpty) acc1.addString(acc2)
else acc1.append(",").addString(acc2)
}
)
लेकिन मुझे संदेह है कि यह सभी उपद्रव के लायक है।
उपरोक्त बातों को ध्यान में रखते हुए, मैंने आपका कोड इस प्रकार फिर से लिखा है:
स्केल :
val input = sc.textFile("train.csv", 6).mapPartitionsWithIndex{
(idx, iter) => if (idx == 0) iter.drop(1) else iter
}
val pairs = input.flatMap(line => line.split(",").zipWithIndex.map{
case ("true", i) => (i, "1")
case ("false", i) => (i, "0")
case p => p.swap
})
val result = pairs.groupByKey.map{
case (k, vals) => {
val valsString = vals.mkString(",")
s"$k,$valsString"
}
}
result.saveAsTextFile("scalaout")
अजगर :
def drop_first_line(index, itr):
if index == 0:
return iter(list(itr)[1:])
else:
return itr
def separate_cols(line):
line = line.replace('true', '1').replace('false', '0')
vals = line.split(',')
for (i, x) in enumerate(vals):
yield (i, x)
input = (sc
.textFile('train.csv', minPartitions=6)
.mapPartitionsWithIndex(drop_first_line))
pairs = input.flatMap(separate_cols)
result = (pairs
.groupByKey()
.map(lambda kv: "{0},{1}".format(kv[0], ",".join(kv[1]))))
result.saveAsTextFile("pythonout")
परिणाम
में local[6]
मोड (इंटेल (आर) जिऑन (आर) सीपीयू E3-1245 V2 @ 3.40GHz) 4GB स्मृति प्रति निष्पादक यह लेता है (n = 3) के साथ:
- स्केल - माध्य: 250.00s, stdev: 12.49
- अजगर - मतलब: 246.66s, stdev: 1.15
मुझे पूरा यकीन है कि उस समय का अधिकांश समय फेरबदल, धारावाहिक, डिसरियलाइजिंग और अन्य माध्यमिक कार्यों पर खर्च होता है। सिर्फ मनोरंजन के लिए, यहां पायथन में भोले एकल-थ्रेडेड कोड है जो एक मिनट से भी कम समय में इस मशीन पर समान कार्य करता है:
def go():
with open("train.csv") as fr:
lines = [
line.replace('true', '1').replace('false', '0').split(",")
for line in fr]
return zip(*lines[1:])