कार्यात्मक प्रोग्रामिंग - अपरिवर्तनीयता महंगी है? [बन्द है]


98

प्रश्न दो भागों में है। पहला वैचारिक है। अगला स्कैला में इसी सवाल पर अधिक सहमति से दिखता है।

  1. एक प्रोग्रामिंग भाषा में केवल अपरिवर्तनीय डेटा संरचनाओं का उपयोग करने से कुछ एल्गोरिदम / तर्क को लागू करना स्वाभाविक रूप से व्यवहार में अधिक कम्प्यूटेशनल रूप से महंगा हो जाता है? यह इस तथ्य की ओर आकर्षित करता है कि अपरिवर्तनीयता विशुद्ध रूप से कार्यात्मक भाषाओं का एक मुख्य सिद्धांत है। क्या ऐसे अन्य कारक हैं जो इसे प्रभावित करते हैं?
  2. आइए एक अधिक ठोस उदाहरण लेते हैं। क्विकॉर्ट को आम तौर पर इन-मेमोरी डेटा संरचना पर म्यूटेबल ऑपरेशन का उपयोग करके सिखाया और कार्यान्वित किया जाता है। म्यूरेबल संस्करण में तुलनीय कम्प्यूटेशनल और स्टोरेज ओवरहेड के साथ PURE में इस तरह की चीज़ को कैसे लागू किया जाता है। विशेष रूप से स्काला में। मैंने नीचे कुछ कच्चे बेंचमार्क शामिल किए हैं।

अधिक जानकारी:

मैं एक अनिवार्य प्रोग्रामिंग पृष्ठभूमि (C ++, जावा) से आता हूं। मैं कार्यात्मक प्रोग्रामिंग की खोज कर रहा हूं, विशेष रूप से स्काला।

शुद्ध कार्यात्मक प्रोग्रामिंग के कुछ प्राथमिक सिद्धांत:

  1. कार्य प्रथम श्रेणी के नागरिक हैं।
  2. कार्यों के दुष्प्रभाव नहीं होते हैं और इसलिए ऑब्जेक्ट / डेटा संरचनाएं अपरिवर्तनीय होती हैं ।

भले ही आधुनिक जेवीएम वस्तु निर्माण के साथ बेहद कुशल हैं और कचरा संग्रह बहुत कम समय तक जीवित वस्तुओं के लिए बहुत सस्ती है, फिर भी वस्तु निर्माण अधिकार को कम से कम करना अभी भी बेहतर है? कम से कम एक एकल-थ्रेडेड एप्लिकेशन में जहां कॉन्सेप्ट और लॉकिंग कोई समस्या नहीं है। चूंकि स्काला एक हाइब्रिड प्रतिमान है, इसलिए यदि आवश्यक हो, तो उत्परिवर्तनीय वस्तुओं के साथ अनिवार्य कोड लिखना चुन सकते हैं। लेकिन, किसी ऐसे व्यक्ति के रूप में जिसने वस्तुओं के पुन: उपयोग और आवंटन को कम करने के लिए बहुत साल बिताए हैं। मुझे लगता है कि ऐसा भी नहीं होने दिया जाएगा सोचा था कि स्कूल की एक अच्छी समझ चाहते हैं।

एक विशिष्ट मामले के रूप में, मुझे इस ट्यूटोरियल 6 में इस कोड स्निपेट से थोड़ा आश्चर्य हुआ । इसका क्विकॉर्ट का जावा संस्करण है, जिसके बाद उसी का एक साफ सुथरा स्कैला कार्यान्वयन है।

यहां कार्यान्वयनों को बेंचमार्क करने का मेरा प्रयास है। मैंने विस्तृत रूपरेखा नहीं बनाई है। लेकिन, मेरा अनुमान है कि स्काला संस्करण धीमा है क्योंकि आवंटित वस्तुओं की संख्या रैखिक है (प्रति पुनरावृत्ति कॉल)। क्या कोई तरीका है जिससे टेल कॉल ऑप्टिमाइज़ेशन खेलने में आ सकता है? अगर मैं सही हूं, तो स्लाला स्व-पुनरावर्ती कॉल के लिए टेल कॉल अनुकूलन का समर्थन करती है। इसलिए, इसे केवल मदद करनी चाहिए। मैं स्काला 2.8 का उपयोग कर रहा हूं।

जावा संस्करण

public class QuickSortJ {

    public static void sort(int[] xs) {
      sort(xs, 0, xs.length -1 );
    }

    static void sort(int[] xs, int l, int r) {
      if (r >= l) return;
      int pivot = xs[l];
      int a = l; int b = r;
      while (a <= b){
        while (xs[a] <= pivot) a++;
        while (xs[b] > pivot) b--;
        if (a < b) swap(xs, a, b);
      }
      sort(xs, l, b);
      sort(xs, a, r);
    }

    static void swap(int[] arr, int i, int j) {
      int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
    }
}

स्काला संस्करण

object QuickSortS {

  def sort(xs: Array[Int]): Array[Int] =
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length / 2)
      Array.concat(
        sort(xs filter (pivot >)),
        xs filter (pivot ==),
        sort(xs filter (pivot <)))
    }
}

कार्यान्वयन की तुलना करने के लिए स्काला कोड

import java.util.Date
import scala.testing.Benchmark

class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {

  val ints = new Array[Int](100000);

  override def prefix = name
  override def setUp = {
    val ran = new java.util.Random(5);
    for (i <- 0 to ints.length - 1)
      ints(i) = ran.nextInt();
  }
  override def run = sortfn(ints)
}

val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut   = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java   " )

benchImmut.main( Array("5"))
benchMut.main( Array("5"))

परिणाम

लगातार पांच रनों के लिए मिलीसेकंड में समय

Immutable/Functional/Scala    467    178    184    187    183
Mutable/Imperative/Java        51     14     12     12     12

10
भोलेपन से लागू होने पर या अनिवार्य भाषाओं के लिए विकसित तरीकों से यह महंगा है। एक स्मार्ट कंपाइलर (उदाहरण के लिए जीएचसी, एक हास्केल कंपाइलर - और हास्केल के पास केवल अपरिवर्तनीय मूल्य हैं) अपरिवर्तनीयता का लाभ उठा सकते हैं और प्रदर्शन को प्राप्त कर सकते हैं जो कि परिवर्तनशीलता का उपयोग करके कोड को प्रतिद्वंद्वी कर सकते हैं। कहने की जरूरत नहीं है, क्विकॉर्ट का भोली कार्यान्वयन अभी भी कुत्ता धीमा है क्योंकि यह अन्य महंगी चीजों, भारी पुनरावृत्ति और O(n)सूची के बीच का उपयोग करता है । हालांकि यह

3
एक महान, संबंधित ब्लॉग लेख यहां है: blogs.sun.com/jrose/entry/larval_objects_in_the_vm उसे प्रोत्साहित करते हैं, क्योंकि इससे जावा के साथ-साथ कार्यात्मक वीएम भाषाओं को बहुत लाभ होगा
andersoj

2
इस SO थ्रेड में कार्यात्मक प्रोग्रामिंग की दक्षता के बारे में बहुत विस्तृत चर्चा है। stackoverflow.com/questions/1990464/… । एक बड़ी बात का जवाब देता है जो मैं जानना चाहता था।
smartnut007

5
यहां सबसे भोली बात आपका बेंचमार्क है। आप उस तरह कोड के साथ कुछ भी बेंचमार्क नहीं कर सकते! किसी भी निष्कर्ष को लेने से पहले आपको JVM पर बेंचमार्क करने पर कुछ लेखों को गंभीरता से पढ़ना चाहिए ... क्या आप जानते हैं कि JVM को चलाने से पहले आपके कोड को JITted नहीं किया होगा? क्या आपने अपने ढेर को उचित और अधिकतम आकार निर्धारित किया है (ताकि आप अधिक मेमोरी के लिए जेवीएम प्रक्रियाओं के समय पर विचार नहीं करेंगे?)। क्या आप जानते हैं कि किन तरीकों को संकलित या फिर से तैयार किया जा रहा है? क्या आप GC के बारे में जानते हैं? आप इस कोड से प्राप्त परिणाम बिल्कुल कुछ भी नहीं मतलब है!
ब्रूनो रीस

2
@userunknown नहीं, यह घोषणात्मक है। इम्पीरेटिव प्रोग्रामिंग "कमांड्स के साथ स्टेट्स को बदलता है" जबकि फंक्शनल प्रोग्रामिंग "डिक्लेयर प्रोग्रामिंग प्रतिमान" है जो "बदलते राज्य से बचा जाता है" ( विकिपीडिया )। तो, हाँ, कार्यात्मक और अनिवार्य दो पूरी तरह से अलग चीजें हैं, और आपके द्वारा लिखा गया कोड अनिवार्य नहीं है।
बजे ब्रायन मैककचटन

जवाबों:


106

चूँकि यहाँ पर कुछ गलत धारणाएँ उड़ रही हैं, इसलिए मैं कुछ बिंदुओं को स्पष्ट करना चाहूँगा।

  • "इन-प्लेस" क्विकॉर्ट वास्तव में इन-प्लेस नहीं है (और क्विकॉर्ट इन-प्लेस परिभाषा के अनुसार नहीं है)। इसे पुनरावर्ती चरण के लिए स्टैक स्थान के रूप में अतिरिक्त भंडारण की आवश्यकता होती है, जो सबसे अच्छे मामले में (लॉग एन ) के क्रम में है , लेकिन सबसे खराब स्थिति में ( एन )।

  • एस्कॉर्ट के एक कार्यात्मक संस्करण को लागू करना जो सरणियों पर संचालित होता है, उद्देश्य को हरा देता है। सरणी कभी अपरिवर्तनीय नहीं होती है।

  • क्विकॉर्ट के "उचित" कार्यात्मक कार्यान्वयन अपरिवर्तनीय सूचियों का उपयोग करता है। यह निश्चित रूप से इन-प्लेस नहीं है, लेकिन इसे प्रक्रियात्मक इन-वर्जन संस्करण के रूप में सबसे खराब स्थिति वाला असममित रनटाइम ( O ( n ^ 2)) और स्पेस जटिलता ( O ( n )) मिला है।

    औसत पर, अपने चलने का समय है अभी भी यथा-स्थान संस्करण (के साथ सममूल्य पर हे ( एन लॉग इन करें n ))। इसकी अंतरिक्ष जटिलता, हालांकि, अभी भी ( एन ) है।


कर रहे हैं दो स्पष्ट नुकसान एक कार्यात्मक quicksort कार्यान्वयन की। निम्नलिखित में, आइए हास्केल परिचय से हास्केल (मुझे पता नहीं है ...) में इस संदर्भ कार्यान्वयन पर विचार करें :

qsort []     = []
qsort (x:xs) = qsort lesser ++ [x] ++ qsort greater
    where lesser  = (filter (< x) xs)
          greater = (filter (>= x) xs)
  1. पहला नुकसान धुरी तत्व की पसंद है , जो बहुत ही अनम्य है। आधुनिक क्विकसर्ट कार्यान्वयन की ताकत पिवट की स्मार्ट पसंद पर बहुत अधिक निर्भर करती है ( बेंटले एट अल द्वारा "इंजीनियरिंग एक सॉर्ट फ़ंक्शन" की तुलना करें )। उपरोक्त एल्गोरिथ्म उस संबंध में खराब है, जो औसत प्रदर्शन को काफी कम कर देता है।

  2. दूसरे, इस एल्गोरिथ्म का उपयोग करता है सूची संयोजन (सूची निर्माण के बजाय) जो एक है हे ( एन ) आपरेशन। यह स्पर्शोन्मुख जटिलता पर असर नहीं करता है लेकिन यह एक औसत दर्जे का कारक है।

एक तीसरा नुकसान कुछ छिपा हुआ है: "इन-प्लेस" वेरिएंट के विपरीत, यह कार्यान्वयन सूची के कॉन्स कोशिकाओं के लिए ढेर से लगातार मेमोरी का अनुरोध करता है और संभावित रूप से सभी जगह मेमोरी को खराब कर देता है। नतीजतन, इस एल्गोरिथ्म में बहुत खराब कैश इलाका है । मुझे नहीं पता कि आधुनिक कार्यात्मक प्रोग्रामिंग भाषाओं में स्मार्ट आवंटनकर्ता इसे कम कर सकते हैं - लेकिन आधुनिक मशीनों पर, कैश मिस एक प्रमुख प्रदर्शन हत्यारा बन गया है।


निष्कर्ष क्या है? दूसरों के विपरीत, मैं यह नहीं कहूंगा कि एस्कॉर्ट स्वाभाविक रूप से अत्यावश्यक है और इसलिए यह एक एफपी वातावरण में बुरी तरह से प्रदर्शन करता है। इसके विपरीत, मैं तर्क दूंगा कि एस्कॉर्ट एक कार्यात्मक एल्गोरिथ्म का एक आदर्श उदाहरण है: यह मूल रूप से एक अपरिवर्तनीय वातावरण में अनुवाद करता है, इसकी अस्वाभाविक चलने का समय और अंतरिक्ष जटिलता प्रक्रियात्मक कार्यान्वयन के बराबर है, और यहां तक ​​कि इसके प्रक्रियात्मक कार्यान्वयन पुनरावृत्ति को नियुक्त करता है।

लेकिन यह एल्गोरिथ्म अभी भी बदतर प्रदर्शन करता है जब एक अपरिवर्तनीय डोमेन के लिए विवश किया जाता है। इसका कारण यह है कि एल्गोरिथ्म में बहुत अधिक (कभी-कभी निम्न-स्तर) फाइन-ट्यूनिंग से लाभ की अजीब संपत्ति होती है जो केवल सरणियों पर कुशलता से किया जा सकता है। क्विकॉर्ट के एक भोले विवरण में इन सभी पेचीदगियों को याद किया जाता है (दोनों कार्यात्मक और प्रक्रियात्मक रूप में)।

"इंजीनियरिंग एक छँटाई समारोह" पढ़ने के बाद मैं अब एक सुंदर एल्गोरिथ्म quicksort पर विचार नहीं कर सकता। कुशलता से लागू किया गया, यह एक भद्दी गड़बड़ है, एक इंजीनियर का काम है, न कि एक कलाकार का (इंजीनियरिंग का अवमूल्यन नहीं करना! इसका अपना सौंदर्य है)।


लेकिन मैं यह भी बताना चाहूंगा कि यह बिंदु विशेष रूप से क्विकॉर्ट है। प्रत्येक एल्गोरिथ्म निम्न-स्तर के ट्विकिंग के समान प्रकार के लिए उत्तरदायी नहीं है। बहुत सारे एल्गोरिदम और डेटा संरचना वास्तव में एक अपरिवर्तनीय वातावरण में प्रदर्शन के नुकसान के बिना व्यक्त की जा सकती हैं।

और अपरिवर्तनीयता महंगी प्रतियों या क्रॉस-थ्रेड सिंक्रोनाइज़ेशन की आवश्यकता को हटाकर प्रदर्शन लागतों को भी कम कर सकती है ।

तो, मूल प्रश्न का उत्तर देने के लिए, “ अपरिवर्तनीयता महंगी है? "- क्विकॉर्ट के विशेष मामले में, एक लागत है जो वास्तव में अपरिवर्तनीयता का परिणाम है। लेकिन सामान्य तौर पर, नहीं


10
+1 - शानदार जवाब! हालांकि मैं व्यक्तिगत रूप से साथ समाप्त हो गया है | कभी कभी के बजाय कोई । फिर भी, यह सिर्फ व्यक्तित्व है - आपने मुद्दों को बहुत अच्छे से समझाया है।
रेक्स केर

6
आपको यह जोड़ना चाहिए कि अपरिवर्तनीय मूल्यों का उपयोग करते हुए एक उचित कार्यान्वयन अनिवार्य रूप से समानांतर संस्करणों के विपरीत समानांतर है। आधुनिक तकनीकी संदर्भ में, यह अधिक से अधिक महत्वपूर्ण हो जाता है।
राफेल

कितना qsort lesser ++ (x : qsort greater)मदद कर रहा है?
सोलोमन उक्यो

42

कार्यात्मक प्रोग्रामिंग के एक बेंचमार्क के रूप में इसके साथ गलत चीजों का एक गुच्छा है। मुख्य विशेषताएं शामिल हैं:

  • आप प्रिमिटिव का उपयोग कर रहे हैं, जिसे बॉक्सिंग / अनबॉक्स करना पड़ सकता है। आप आदिम वस्तुओं को लपेटने के ओवरहेड का परीक्षण करने की कोशिश नहीं कर रहे हैं, आप अपरिवर्तनीयता का परीक्षण करने की कोशिश कर रहे हैं।
  • आपने एक एल्गोरिथ्म चुना है जहाँ इन-प्लेस ऑपरेशन असामान्य रूप से प्रभावी है (और निश्चित रूप से ऐसा)। यदि आप यह दिखाना चाहते हैं कि ऐसे एल्गोरिदम मौजूद हैं जो पारस्परिक रूप से लागू होने पर तेज़ होते हैं, तो यह एक अच्छा विकल्प है; अन्यथा, यह भ्रामक होने की संभावना है।
  • आप गलत टाइमिंग फ़ंक्शन का उपयोग कर रहे हैं। का उपयोग करें System.nanoTime
  • बेंचमार्क आपके लिए आश्वस्त होने के लिए बहुत छोटा है कि JIT संकलन मापा समय का महत्वपूर्ण हिस्सा नहीं होगा।
  • सरणी एक कुशल तरीके से विभाजित नहीं है।
  • ऐरे म्यूट हैं, इसलिए एफपी के साथ उनका उपयोग करना एक अजीब तुलना है।

तो, यह तुलना एक महान चित्रण है जिसे आपको उच्च-प्रदर्शन कोड लिखने के लिए अपनी भाषा (और एल्गोरिथ्म) को विस्तार से समझना होगा। लेकिन यह एफपी बनाम गैर-एफपी की बहुत अच्छी तुलना नहीं है। यदि आप चाहते हैं कि, कंप्यूटर लैंग्वेज बेंचमार्क गेम में हास्केल बनाम सी ++ देखें । टेक-होम संदेश यह है कि जुर्माना आमतौर पर 2 या 3 या उससे अधिक के कारक से अधिक नहीं है, लेकिन यह वास्तव में निर्भर करता है। (कोई वादा नहीं है कि हास्केल लोगों ने सबसे तेज एल्गोरिदम लिखा है या तो संभव है, लेकिन कम से कम उनमें से कुछ ने शायद कोशिश की है! फिर से, कुछ हास्केल सी लाइब्रेरी कहते हैं ....)

अब, मान लीजिए कि आप क्विकसॉर्ट का अधिक उचित बेंचमार्क चाहते हैं, यह पहचानते हुए कि यह संभवतः एफपी बनाम उत्परिवर्ती एल्गोरिदम के लिए सबसे खराब मामलों में से एक है, और डेटा-संरचना मुद्दे की अनदेखी कर रहा है (यानी यह दिखावा कि हमारे पास एक अपरिवर्तनीय ऐरे हो सकता है:

object QSortExample {
  // Imperative mutable quicksort
  def swap(xs: Array[String])(a: Int, b: Int) {
    val t = xs(a); xs(a) = xs(b); xs(b) = t
  }
  def muQSort(xs: Array[String])(l: Int = 0, r: Int = xs.length-1) {
    val pivot = xs((l+r)/2)
    var a = l
    var b = r
    while (a <= b) {
      while (xs(a) < pivot) a += 1
      while (xs(b) > pivot) b -= 1
      if (a <= b) {
        swap(xs)(a,b)
        a += 1
        b -= 1
      }
    }
    if (l<b) muQSort(xs)(l, b)
    if (b<r) muQSort(xs)(a, r)
  }

  // Functional quicksort
  def fpSort(xs: Array[String]): Array[String] = {
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length/2)
      val (small,big) = xs.partition(_ < pivot)
      if (small.length == 0) {
        val (bigger,same) = big.partition(_ > pivot)
        same ++ fpSort(bigger)
      }
      else fpSort(small) ++ fpSort(big)
    }
  }

  // Utility function to repeat something n times
  def repeat[A](n: Int, f: => A): A = {
    if (n <= 1) f else { f; repeat(n-1,f) }
  }

  // This runs the benchmark
  def bench(n: Int, xs: Array[String], silent: Boolean = false) {
    // Utility to report how long something took
    def ptime[A](f: => A) = {
      val t0 = System.nanoTime
      val ans = f
      if (!silent) printf("elapsed: %.3f sec\n",(System.nanoTime-t0)*1e-9)
      ans
    }

    if (!silent) print("Scala builtin ")
    ptime { repeat(n, {
      val ys = xs.clone
      ys.sorted
    }) }
    if (!silent) print("Mutable ")
    ptime { repeat(n, {
      val ys = xs.clone
      muQSort(ys)()
      ys
    }) }
    if (!silent) print("Immutable ")
    ptime { repeat(n, {
      fpSort(xs)
    }) }
  }

  def main(args: Array[String]) {
    val letters = (1 to 500000).map(_ => scala.util.Random.nextPrintableChar)
    val unsorted = letters.grouped(5).map(_.mkString).toList.toArray

    repeat(3,bench(1,unsorted,silent=true))  // Warmup
    repeat(3,bench(10,unsorted))     // Actual benchmark
  }
}

कार्यात्मक Quicksort में संशोधन पर ध्यान दें ताकि यह केवल डेटा के माध्यम से एक बार यदि संभव हो, और अंतर्निहित सॉर्ट की तुलना में हो सके। जब हम इसे चलाते हैं तो हमें कुछ ऐसा मिलता है:

Scala builtin elapsed: 0.349 sec
Mutable elapsed: 0.445 sec
Immutable elapsed: 1.373 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.441 sec
Immutable elapsed: 1.374 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.442 sec
Immutable elapsed: 1.383 sec

इसलिए, यह सीखने से कि अपनी तरह से लिखने की कोशिश करना एक बुरा विचार है, हम पाते हैं कि अपरिवर्तनीय एस्कॉर्ट के लिए ~ 3x जुर्माना है अगर बाद को कुछ सावधानी से लागू किया जाता है। (आप एक त्रिविजयी विधि भी लिख सकते हैं जो तीन सरणियों को लौटाती है: वे जिनकी तुलना में कम हैं, जो समान हैं, और वे धुरी से अधिक हैं। इससे चीजें थोड़ी और बढ़ सकती हैं।)


बस बॉक्सिंग / अनबॉक्सिंग के बारे में। यदि कुछ भी हो, तो यह जावा पक्ष पर जुर्माना होना चाहिए? स्केला (बनाम इंटेगर) के लिए पसंदीदा अंक प्रकार नहीं है। इसलिए, स्कैला की तरफ कोई बॉक्सिंग खुश नहीं है। बॉक्सिंग केवल जावा पक्ष पर एक मुद्दा है क्योंकि ऑटोबॉक्सिंग फॉर्म scala Int to java.lang.Integer / int है। यहाँ एक लिंक है जो इस विषय के बारे में विस्तार से बताता
smartnut007

हां, मैं यहां शैतानों के वकील की भूमिका निभा रहा हूं। म्यूटेबिलिटी क्विकॉर्ट्स डिजाइन का एक अंतरग्रहीय हिस्सा है। यही कारण है कि मैं समस्या के शुद्ध कार्यात्मक दृष्टिकोण के बारे में बहुत उत्सुक था। आह, मैंने इस कथन को धागे पर 10 वीं बार कहा है :-)। आपके बाकी पोस्ट को देखूंगा जब मैं उठूंगा और वापस आऊंगा। धन्यवाद।
smartnut007

2
@ smartnut007 - स्काला संग्रह सामान्य हैं। जेनरिक को अधिकांश भाग के लिए बॉक्सिंग प्रकारों की आवश्यकता होती है (हालांकि कुछ आदिम प्रकारों के लिए उन्हें विशेषज्ञ बनाने का प्रयास चल रहा है)। इसलिए आप सभी निफ्टी संग्रह विधियों का उपयोग नहीं कर सकते हैं और मान सकते हैं कि जब आप उनके माध्यम से आदिम प्रकार के संग्रह पास करते हैं तो कोई जुर्माना नहीं होगा। यह काफी संभावना है कि आदिम प्रकार को रास्ते में बॉक्सिंग करना होगा और बाहर के रास्ते पर अनबॉक्स करना होगा।
रेक्स केर

मुझे यह तथ्य पसंद नहीं है कि आपने जो शीर्ष दोष बताया है वह सिर्फ एक अटकल है :-)
smartnut007

1
@ smartnut007 - यह एक शीर्ष दोष है क्योंकि यह जांचना मुश्किल है, और अगर सच में परिणाम सामने आता है। यदि आप निश्चित हैं कि मुक्केबाजी नहीं है, तो मैं मानता हूं कि दोष मान्य नहीं है। कमी थी जिसके कारण वहाँ नहीं है है मुक्केबाजी, यह आपको लगता है कि है पता नहीं है वहाँ मुक्केबाजी चाहे (और मुझे यकीन है कि या तो नहीं कर रहा हूँ - विशेषज्ञता इस यह पता लगाने की मुश्किल बना दिया है)। जावा पक्ष (या स्काला म्यूटेबल कार्यान्वयन) पर कोई बॉक्सिंग नहीं है क्योंकि आप केवल प्राइमेटिव का उपयोग करते हैं। वैसे भी, एक अपरिवर्तनीय संस्करण एन लॉग एन स्पेस के माध्यम से काम करता है, इसलिए आप वास्तव में मेमोरी आवंटन के साथ तुलना / स्वैप की लागत की तुलना करते हैं।
रेक्स केर

10

मुझे नहीं लगता कि स्काला संस्करण वास्तव में पूंछ पुनरावर्ती है, क्योंकि आप उपयोग कर रहे हैं Array.concat

इसके अलावा, सिर्फ इसलिए कि यह मुहावरेदार स्काला कोड है, इसका मतलब यह नहीं है कि यह सबसे अच्छा तरीका है।

ऐसा करने का सबसे अच्छा तरीका स्कैला के अंतर्निहित छँटाई कार्यों में से एक का उपयोग करना होगा। इस तरह आपको अपरिवर्तनीयता की गारंटी मिलती है और आपको पता चलता है कि आपके पास एक तेज़ एल्गोरिथम है।

स्टैक ओवरफ्लो प्रश्न देखें मैं स्काला में एक सरणी को कैसे सॉर्ट करूं? एक उदाहरण के लिए।


4
इसके अलावा, मुझे नहीं लगता कि एक पूंछ पुनरावर्ती त्वरित प्रकार संभव है, जैसा कि आपको दो पुनरावर्ती कॉल करना है
एलेक्स लो

1
यह संभव है, आपको अपने ढेर पर ढेर-फ्रेम-स्टैक-फ्रेम उठाने के लिए निरंतरता का उपयोग करना होगा।
ब्रायन

inbuilt scala.util.Sorting.quickSort (सरणी) सरणी को बदल देता है। यह जावा के रूप में उपवास करता है, आश्चर्यजनक रूप से नहीं। मुझे एक कुशल शुद्ध कार्यात्मक समाधान में दिलचस्पी है। यदि नहीं, तो क्यों। क्या यह सामान्य रूप से स्काला या कार्यात्मक प्रतिमान की सीमा है। उस तरह की बात।
smartnut007

@ smartnut007: स्काला के किस संस्करण का आप उपयोग कर रहे हैं? स्कैला 2.8 में, आप वह कर सकते हैं array.sortedजो एक नए क्रमबद्ध सरणी को लौटाता है, मूल को म्यूट नहीं करता है।
लापता

@AlexLo - एक पूंछ पुनरावर्ती त्वरित प्रकार संभव है। कुछ इस तरह:TAIL-RECURSIVE-QUICKSORT(Array A, int lo, int hi): while p < r: q = PARTITION(A, lo, hi); TAIL-RECURSIVE-QUICKSORT(A, lo, q - 1); p = q + 1;
जेकवे

8

अपरिवर्तनशीलता महंगी नहीं है। यह सुनिश्चित करना महंगा हो सकता है यदि आप उन कार्यों का एक छोटा सबसेट मापते हैं जो एक प्रोग्राम को करना है, और बूट करने के लिए उत्परिवर्तन के आधार पर एक समाधान चुनना है - जैसे कि एस्कॉर्ट को मापना।

इसे सीधे शब्दों में कहें, तो विशुद्ध रूप से कार्यात्मक भाषाओं का उपयोग करते समय आप जल्दी नहीं करते हैं।

इसे दूसरे कोण से समझते हैं। आइए इन दो कार्यों पर विचार करें:

// Version using mutable data structures
def tailFrom[T : ClassManifest](arr: Array[T], p: T => Boolean): Array[T] = {
  def posIndex(i: Int): Int = {
    if (i < arr.length) {
      if (p(arr(i)))
        i
      else
        posIndex(i + 1)
    } else {
      -1
    }
  }

  var index = posIndex(0)

  if (index < 0) Array.empty
  else {
    var result = new Array[T](arr.length - index)
    Array.copy(arr, index, result, 0, arr.length - index)
    result
  }
}

// Immutable data structure:

def tailFrom[T](list: List[T], p: T => Boolean): List[T] = {
  def recurse(sublist: List[T]): List[T] = {
    if (sublist.isEmpty) sublist
    else if (p(sublist.head)) sublist
    else recurse(sublist.tail)
  }
  recurse(list)
}

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

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

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


7

एक सरणी को क्रमबद्ध करना, जैसे, ब्रह्मांड में सबसे अनिवार्य कार्य है। यह आश्चर्यजनक नहीं है कि कई सुरुचिपूर्ण 'अपरिवर्तनीय' रणनीतियाँ / कार्यान्वयन 'सॉर्ट एरे' माइक्रोबेनमार्क पर खराब रूप से विफल होते हैं। हालांकि इसका मतलब यह नहीं है कि अपरिवर्तनशीलता "सामान्य रूप से" महंगी है। ऐसे कई कार्य हैं, जहाँ अपरिवर्तनीय कार्यान्वयन, म्यूट करने वालों की तुलना में बेहतर प्रदर्शन करेंगे, लेकिन अक्सर छांटने वाला सरणी उनमें से एक नहीं है।


7

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


क्या आप स्काला में एक कार्यान्वयन प्रदान करने के लिए पर्याप्त हो सकते हैं।
smartnut007

3
powells.com/biblio/17-0521631246-0 (क्रिस ओकासाकी द्वारा विशुद्ध रूप से कार्यात्मक डेटा संरचनाएं) - बस इस पुस्तक के माध्यम से देखें। प्रभावी एल्गोरिदम और डेटा संरचनाओं को लागू करते समय कार्यात्मक प्रोग्रामिंग लाभों का लाभ उठाने के लिए एक मजबूत कहानी है।
वासिल रेमेनीयुक

1
code.google.com/p/ देबाशीष घोष द्वारा
स्कैला

क्या आप समझा सकते हैं कि आपको क्यों लगता है कि स्काला अनिवार्य नहीं है? list.filter (foo).sort (bar).take (10)- क्या अधिक जरूरी हो सकता है?
उपयोगकर्ता अज्ञात

7

स्काला में अपरिवर्तनीयता की लागत

यहां एक संस्करण है जो जावा एक की तुलना में लगभग तेज है। ;)

object QuickSortS {
  def sort(xs: Array[Int]): Array[Int] = {
    val res = new Array[Int](xs.size)
    xs.copyToArray(res)
    (new QuickSortJ).sort(res)
    res
  }
}

यह संस्करण सरणी की एक प्रति बनाता है, इसे जावा संस्करण का उपयोग करके जगह में क्रमबद्ध करता है और प्रतिलिपि लौटाता है। स्काला आपको आंतरिक रूप से अपरिवर्तनीय संरचना का उपयोग करने के लिए मजबूर नहीं करता है।

तो स्केला का लाभ यह है कि आप उपयुक्तता और अपरिवर्तनीयता का लाभ उठा सकते हैं जैसा कि आप फिट देखते हैं। नुकसान यह है कि अगर आप गलत करते हैं तो आपको वास्तव में अपरिवर्तनीयता का लाभ नहीं मिलता है।


हालांकि यह प्रश्न का सटीक उत्तर नहीं है, लेकिन मुझे लगता है कि यह किसी भी अच्छे उत्तर का हिस्सा है: एक टिकाऊ संरचना का उपयोग करते समय क्विकॉर्ट तेज़ है। लेकिन अपरिवर्तनीयता का मुख्य लाभ इंटरफ़ेस है, और स्काला में कम से कम आपके पास दोनों हो सकते हैं। क्विकॉर्ट के लिए म्यूटेबिलिटी तेज होती है, लेकिन यह आपके प्रदर्शन करने वाले लिखने के तरीके में खड़ा नहीं होता है, ज्यादातर अपरिवर्तनीय कोड।
पॉल ड्रेपर

7

क्विकॉर्ट को इन-प्लेस के दौरान तेजी से जाना जाता है, इसलिए यह शायद ही उचित तुलना है!

कहा जा रहा है कि ... Array.concat? यदि और कुछ नहीं है, तो आप दिखा रहे हैं कि जब आप कोशिश करते हैं और कार्यात्मक एल्गोरिथ्म में इसका उपयोग करते हैं, तो अनिवार्य प्रोग्रामिंग के लिए अनुकूलित एक संग्रह प्रकार विशेष रूप से धीमा है; लगभग किसी भी अन्य विकल्प तेजी से होगा!


एक और बहुत महत्वपूर्ण बात पर विचार करना है, शायद सबसे महत्वपूर्ण मुद्दा है जब दो की तुलना दृष्टिकोण है: "कितनी अच्छी तरह से अधिक नोड्स / कोर के लिए बाहर इस पैमाने करता है"

संभावना है, यदि आप एक अपरिवर्तनीय एस्कॉर्ट की तलाश कर रहे हैं तो आप ऐसा कर रहे हैं क्योंकि आप वास्तव में एक समानांतर एस्कॉर्ट चाहते हैं। इस विषय पर विकिपीडिया के कुछ उद्धरण हैं: http://en.wikipedia.org/wiki/Quicksort#Parallelifications

स्कैला संस्करण फ़ंक्शन को पुन: भेजने से पहले बस कांटा कर सकता है, अगर यह आपके पास पर्याप्त कोर उपलब्ध होने पर अरबों प्रविष्टियों वाली सूची को सॉर्ट करने की अनुमति देता है।

अभी, मेरे सिस्टम में GPU के पास 128 कोर उपलब्ध हैं यदि मैं इस पर केवल स्काला कोड चला सकता हूं, और यह वर्तमान पीढ़ी से दो साल पीछे एक साधारण डेस्कटॉप सिस्टम पर है।

मुझे आश्चर्य है कि एकल-पिरोया अनिवार्य दृष्टिकोण के खिलाफ कैसे ढेर होगा ...

शायद अधिक महत्वपूर्ण सवाल इसलिए है:

"यह देखते हुए कि व्यक्तिगत कोर किसी भी तेजी से नहीं जा रहे हैं, और सिंक्रनाइज़ेशन / लॉकिंग समानांतरता के लिए एक वास्तविक चुनौती प्रस्तुत करता है, क्या पारस्परिकता महंगी है?"


वहां कोई तर्क नहीं। Quicksort परिभाषा में एक मेमोरी सॉर्ट है। मुझे यकीन है कि ज्यादातर लोग कॉलेज से याद करते हैं। लेकिन, आप शुद्ध कार्यात्मक तरीके से क्विकसर्ट कैसे करते हैं। यानी बिना साइड इफेक्ट के।
smartnut007

इसका महत्वपूर्ण कारण, ऐसे दावे हैं कि कार्यात्मक प्रतिमान केवल साइड इफेक्ट्स के साथ कार्य के रूप में तेज़ हो सकते हैं।
smartnut007

सूची संस्करण आधे से समय कम कर देता है। फिर भी, जावा संस्करण की गति के पास कोई भी नहीं।
smartnut007

क्या आप समझा सकते हैं कि आपको क्यों लगता है कि स्काला अनिवार्य नहीं है? list.filter (foo).sort (bar).take (10)- क्या अधिक जरूरी हो सकता है? धन्यवाद।
अज्ञात अज्ञात

@user अज्ञात: शायद आप स्पष्ट कर सकते हैं कि आपको क्या लगता है कि "अनिवार्यता" का अर्थ है, क्योंकि आपका कहा गया उदाहरण मुझे अच्छी तरह से कार्यात्मक लगता है। स्काला अपने आप में न तो अनिवार्य है और न ही घोषणात्मक है, भाषा दोनों शैलियों का समर्थन करती है और इन शब्दों का उपयोग विशिष्ट उदाहरणों का वर्णन करने के लिए किया जाता है।
केविन राइट

2

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


क्या आप समझा सकते हैं कि आपको क्यों लगता है कि स्काला अनिवार्य नहीं है? list.filter (foo).sort (bar).take (10)- क्या अधिक जरूरी हो सकता है? धन्यवाद।
उपयोगकर्ता अज्ञात

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