स्काला बनाम पायथन के लिए स्पार्क प्रदर्शन


178

मैं स्काला के ऊपर अजगर को पसंद करता हूं। लेकिन, जैसा कि स्पार्क को मूल रूप से स्काला में लिखा गया है, मैं स्पष्ट कारणों से अपने कोड को स्काला में पायथन संस्करण की तुलना में तेजी से चलाने की उम्मीद कर रहा था।

उस धारणा के साथ, मैंने कुछ 1 जीबी डेटा के लिए कुछ बहुत ही सामान्य प्रीप्रोसेसिंग कोड के स्काला संस्करण को सीखना और लिखना सोचा। डेटा को कागल पर स्प्रिंगलेफ़ प्रतियोगिता से चुना गया है । बस डेटा का अवलोकन देने के लिए (इसमें 1936 आयाम और 145232 पंक्तियाँ हैं)। डेटा विभिन्न प्रकारों से बना है जैसे इंट, फ्लोट, स्ट्रिंग, बूलियन। मैं स्पार्क प्रसंस्करण के लिए 8 में से 6 कोर का उपयोग कर रहा हूं; इसलिए मैंने minPartitions=6इसलिए इस्तेमाल किया ताकि हर कोर को कुछ न कुछ प्रोसेस करना पड़े।

स्काला कोड

val input = sc.textFile("train.csv", minPartitions=6)

val input2 = input.mapPartitionsWithIndex { (idx, iter) => 
  if (idx == 0) iter.drop(1) else iter }
val delim1 = "\001"

def separateCols(line: String): Array[String] = {
  val line2 = line.replaceAll("true", "1")
  val line3 = line2.replaceAll("false", "0")
  val vals: Array[String] = line3.split(",")

  for((x,i) <- vals.view.zipWithIndex) {
    vals(i) = "VAR_%04d".format(i) + delim1 + x
  }
  vals
}

val input3 = input2.flatMap(separateCols)

def toKeyVal(line: String): (String, String) = {
  val vals = line.split(delim1)
  (vals(0), vals(1))
}

val input4 = input3.map(toKeyVal)

def valsConcat(val1: String, val2: String): String = {
  val1 + "," + val2
}

val input5 = input4.reduceByKey(valsConcat)

input5.saveAsTextFile("output")

पायथन कोड

input = sc.textFile('train.csv', minPartitions=6)
DELIM_1 = '\001'


def drop_first_line(index, itr):
  if index == 0:
    return iter(list(itr)[1:])
  else:
    return itr

input2 = input.mapPartitionsWithIndex(drop_first_line)

def separate_cols(line):
  line = line.replace('true', '1').replace('false', '0')
  vals = line.split(',')
  vals2 = ['VAR_%04d%s%s' %(e, DELIM_1, val.strip('\"'))
           for e, val in enumerate(vals)]
  return vals2


input3 = input2.flatMap(separate_cols)

def to_key_val(kv):
  key, val = kv.split(DELIM_1)
  return (key, val)
input4 = input3.map(to_key_val)

def vals_concat(v1, v2):
  return v1 + ',' + v2

input5 = input4.reduceByKey(vals_concat)
input5.saveAsTextFile('output')

स्काला प्रदर्शन स्टेज 0 (38 मिनट), स्टेज 1 (18 सेकंड) यहां छवि विवरण दर्ज करें

पायथन प्रदर्शन स्टेज 0 (11 मिनट), स्टेज 1 (7 सेकंड) यहां छवि विवरण दर्ज करें

दोनों अलग DAG दृश्य रेखांकन पैदा करता है (जिसके कारण दोनों चित्रों स्काला (के लिए अलग मंच 0 कार्यों दिखाने map) और अजगर ( reduceByKey))

लेकिन, अनिवार्य रूप से दोनों कोड डेटा को (आयाम_id, मूल्यों की सूची की स्ट्रिंग) RDD में बदलने और डिस्क पर सहेजने का प्रयास करते हैं। आउटपुट का उपयोग प्रत्येक आयाम के लिए विभिन्न आँकड़ों की गणना करने के लिए किया जाएगा।

प्रदर्शन के अनुसार, इस वास्तविक डेटा के लिए स्काला कोड इस तरह लगता है कि पायथन संस्करण की तुलना में 4 गुना धीमा है। मेरे लिए अच्छी खबर यह है कि इसने मुझे पायथन के साथ रहने के लिए अच्छी प्रेरणा दी। बुरी खबर यह है कि मुझे समझ में क्यों नहीं आया?


8
शायद यह कोड और अनुप्रयोग पर निर्भर है क्योंकि मुझे दूसरा परिणाम मिलता है, कि अपाचे स्पार्क अजगर स्कैला की तुलना में धीमा है, जब n
पॉल

3
दिलचस्प सवाल! Btw, यहां भी एक नज़र डालें: emptypipes.org/2015/01/17/python-vs-scala-vs-spark आपके पास जितनी अधिक कोर हैं, आप भाषाओं के बीच अंतर कम देख सकते हैं।
मार्कॉन

क्या आपने मौजूदा उत्तर को स्वीकार करने पर विचार किया है ?
10465355

जवाबों:


358

कोड पर चर्चा करने वाला मूल उत्तर नीचे पाया जा सकता है।


सबसे पहले, आपको विभिन्न प्रकार के एपीआई के बीच अंतर करना होगा, प्रत्येक को अपने स्वयं के प्रदर्शन के विचारों के साथ।

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:])

23
सबसे स्पष्ट, व्यापक और उपयोगी उत्तरों में से एक जो मैंने थोड़ी देर के लिए सामना किया है। धन्यवाद!
एतोव

क्या शानदार लड़का है तू!
डेनिसली

-4

उपरोक्त उत्तरों के लिए विस्तार -

स्काला अजगर की तुलना में कई मायनों में तेज साबित होता है, लेकिन कुछ मान्य कारण हैं कि अजगर अधिक लोकप्रिय हो रहा है, क्योंकि स्केला, उनमें से कुछ को देखते हैं -

अपाचे स्पार्क के लिए पायथन सीखने और उपयोग करने के लिए बहुत आसान है। हालांकि, यह एकमात्र कारण नहीं है कि पाइसपार्क स्काला से बेहतर विकल्प क्यों है। अभी और है।

स्पार्क के लिए पायथन एपीआई क्लस्टर पर धीमी हो सकती है, लेकिन अंत में, स्काला की तुलना में डेटा वैज्ञानिक इसके साथ बहुत कुछ कर सकते हैं। स्काला की जटिलता अनुपस्थित है। इंटरफ़ेस सरल और व्यापक है।

Apache Spark के लिए Python API के साथ कोड, रखरखाव और परिचितता की पठनीयता के बारे में बात करना Scala से कहीं बेहतर है।

पायथन मशीन सीखने और प्राकृतिक भाषा प्रसंस्करण से संबंधित कई पुस्तकालयों के साथ आता है। यह डेटा विश्लेषण में सहायता करता है और इसमें आंकड़े भी होते हैं जो बहुत परिपक्व और समय-परीक्षण किए जाते हैं। उदाहरण के लिए, सुन्न, पांडा, डरावना-सीखें, समुद्री और मैटलोट्लिब।

नोट: अधिकांश डेटा वैज्ञानिक एक संकर दृष्टिकोण का उपयोग करते हैं जहां वे दोनों एपीआई का सबसे अच्छा उपयोग करते हैं।

अंत में, स्काला समुदाय अक्सर प्रोग्रामर के लिए बहुत कम मददगार साबित होता है। यह अजगर को बहुत मूल्यवान सीख देता है। यदि आपके पास जावा जैसी किसी भी टाइप की गई प्रोग्रामिंग भाषा के साथ पर्याप्त अनुभव है, तो आप स्काला का पूरी तरह से उपयोग नहीं करने के बारे में चिंता करना बंद कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.