LINQ विधियों की रन-टाइम जटिलता (Big-O) पर क्या गारंटी है?


120

मैंने हाल ही में LINQ का उपयोग करना शुरू किया है, और मैंने वास्तव में LINQ विधियों में से किसी के लिए रन-टाइम जटिलता का कोई उल्लेख नहीं देखा है। जाहिर है, यहां खेलने के कई कारक हैं, इसलिए आइए चर्चा को सादे IEnumerableLINQ-to-Objects प्रदाता तक सीमित करें । इसके अलावा, मान लें कि Funcचयनकर्ता / उत्परिवर्ती / आदि के रूप में पारित किसी भी एक सस्ता ओ (1) ऑपरेशन है।

यह स्पष्ट है कि सभी एक-पास परिचालन (लगता है Select, Where, Count, Take/Skip, Any/All, आदि) हे (एन) के बाद से वे केवल एक बार अनुक्रम चलने के लिए की जरूरत है हो जाएगा,; हालांकि यह भी आलस्य के अधीन है।

अधिक जटिल कार्यों के लिए चीजें मुखर हैं; सेट-जैसे ऑपरेटर ( Unionऔर Distinct, Exceptआदि) GetHashCodeडिफ़ॉल्ट (afaik) का उपयोग करके काम करते हैं , इसलिए यह मान लेना उचित है कि वे हैश-टेबल का आंतरिक रूप से उपयोग कर रहे हैं, जिससे ये ऑपरेशन O (n) के साथ-साथ सामान्य रूप से भी हो रहे हैं। उन संस्करणों के बारे में जो एक का उपयोग करते हैं IEqualityComparer?

OrderByएक प्रकार की आवश्यकता होगी, इसलिए सबसे अधिक संभावना है कि हम ओ (एन लॉग एन) को देख रहे हैं। यदि यह पहले से ही हल है तो क्या होगा? कैसे के बारे में अगर मैं कहता हूं OrderBy().ThenBy()और दोनों को एक ही कुंजी प्रदान करता हूं ?

मैं या तो छँटाई, या हैशिंग का उपयोग कर GroupBy(और Join) देख सकता था । यह किसका है?

Containsहो सकता है O (n) a पर List, लेकिन O (1) a HashSet- पर LINQ अंतर्निहित कंटेनर की जांच करता है कि क्या वह चीजों को गति दे सकता है?

और असली सवाल - अब तक, मैं इसे विश्वास पर ले रहा हूं कि संचालन प्रदर्शन कर रहे हैं। हालांकि, क्या मैं उस पर बैंक कर सकता हूं? एसटीएल कंटेनर, उदाहरण के लिए, प्रत्येक ऑपरेशन की जटिलता को स्पष्ट रूप से निर्दिष्ट करते हैं। क्या .NET लाइब्रेरी विनिर्देश में LINQ प्रदर्शन पर कोई समान गारंटी है?

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


यद्यपि मैं वास्तव में आपके प्रश्न का उत्तर नहीं दे सकता हूं, लेकिन मैं यह टिप्पणी करना चाहता हूं कि सामान्य रूप से प्रदर्शन का बड़ा हिस्सा कोर कार्यक्षमता की तुलना में "ओवरहेड" होगा। यह निश्चित रूप से मामला नहीं है जब आपके पास बहुत बड़े डेटासेट (> 10k आइटम) हों, तो im उत्सुक हैं कि आप किस मामले में जानना चाहते हैं।
हेनरी

2
पुन: "यदि आप घोषणात्मक या कार्यात्मक वाक्यविन्यास का उपयोग कर रहे हैं तो यह अलग है?" - कंपाइलर डिक्लेक्टिव सिंटैक्स को फंक्शनल सिंटैक्स में ट्रांसलेट करता है, इसलिए वे समान होंगे।
जॉन राश

"STL कंटेनर स्पष्ट रूप से हर ऑपरेशन की जटिलता को निर्दिष्ट करते हैं" .NET कंटेनर स्पष्ट रूप से हर ऑपरेशन की जटिलता को भी निर्दिष्ट करते हैं। Linq एक्सटेंशन STL एल्गोरिदम के समान हैं, STL कंटेनरों के लिए नहीं। ठीक उसी तरह जब आप एसटीएल कंटेनर में एसटीएल एल्गोरिदम लागू करते हैं, तो आपको परिणामी जटिलता का सही विश्लेषण करने के लिए .NET कंटेनर ऑपरेशन (एस) की जटिलता के साथ लाइनक एक्सटेंशन की जटिलता को संयोजित करने की आवश्यकता होती है। इसमें टेम्प्लेट स्पेशलाइज़ेशन के लिए लेखांकन शामिल है, जैसा कि आरोन्यूज़ के उत्तर में उल्लेख है।
टिम्बो

एक अंतर्निहित प्रश्न यह है कि Microsoft अधिक चिंतित क्यों नहीं था कि एक IList <T> अनुकूलन सीमित उपयोगिता का होगा, यह देखते हुए कि एक डेवलपर को अनिर्दिष्ट व्यवहार पर भरोसा करना होगा यदि उसका कोड उस पर निर्भर करता है कि वह प्रदर्शन करने वाला है।
एडवर्ड ब्रे

परिणामी सेट सूची पर AsParallel (); आपको ~ O (1) <O (n)
लेटेंसी

जवाबों:


121

बहुत, बहुत कम गारंटी हैं, लेकिन कुछ अनुकूलन हैं:

  • जैसे एक्सटेंशन तरीकों कि अनुक्रमित उपयोग का उपयोग, ElementAt, Skip, Lastया LastOrDefault, चाहे या नहीं अंतर्निहित प्रकार लागू करता है देखने के लिए जाँच करेगा IList<T>, ताकि आप हे (1) का उपयोग करने के बजाय मिल हे (एन) के।

  • Countएक के लिए विधि चेकों ICollectionकार्यान्वयन, इसलिए हे (1) के बजाय है कि इस ऑपरेशन है हे (एन)।

  • Distinct, GroupBy Joinहै, और मैं यह भी मानना है सेट एकत्रीकरण विधियों ( Union, Intersectऔर Except) उपयोग हैशिंग, तो वे हे (एन) के बजाय ओ (n²) के करीब होना चाहिए।

  • ContainsICollectionकार्यान्वयन के लिए जाँच , इसलिए यह O (1) हो सकता है यदि अंतर्निहित संग्रह भी O (1) है, जैसे कि HashSet<T>, लेकिन यह वास्तविक डेटा संरचना पर निर्भर करता है और इसकी गारंटी नहीं है। हैश Containsविधि को ओवरराइड करता है, यही कारण है कि वे ओ (1) हैं।

  • OrderBy विधियाँ एक स्थिर क्विकॉर्टोर्ट का उपयोग करती हैं, इसलिए वे O (N log N) औसत केस हैं।

मुझे लगता है कि बिल्ट-इन एक्सटेंशन के सभी तरीकों को शामिल नहीं किया गया है। वास्तव में बहुत कम प्रदर्शन की गारंटी है; Linq ही कुशल डेटा संरचनाओं का लाभ उठाने की कोशिश करेगा, लेकिन संभावित अक्षम कोड लिखने के लिए यह एक मुफ्त पास नहीं है।


IEqualityComparerओवरलोड के बारे में कैसे ?
तजमान

@tzaman: उनके बारे में क्या? जब तक आप एक बहुत ही अयोग्य प्रथा का उपयोग IEqualityComparerनहीं करते हैं , मैं इसे विषमता की जटिलता को प्रभावित करने का कारण नहीं बना सकता।
हारून

1
अरे हाँ। मैं के रूप में अच्छी तरह से EqualityComparerलागू नहीं महसूस किया था ; लेकिन बिल्कुल सही समझ में आता है। GetHashCodeEquals
तजमान

2
@imgen: लूप जॉन्स O (N * M) हैं जो असंबंधित सेटों के लिए O (N:) का सामान्यीकरण करते हैं। Linq हैश जॉन्स का उपयोग करता है जो O (N + M) हैं, जो O (N) का सामान्यीकरण करता है। यह एक आधे सभ्य सभ्य हैश फ़ंक्शन मानता है, लेकिन .NET में गड़बड़ करना मुश्किल है।
Aaronaught

1
है Orderby().ThenBy()अब भी N logNहै या यह है (N logN) ^2या कुछ है कि पसंद है?
एम। केज़म अख़गरी

10

मैं लंबे समय से जानता हूं कि .Count()रिटर्न .Countअगर गणन एक है IList

लेकिन मैं हमेशा एक सा सेट आपरेशन के रन-टाइम जटिलता के बारे में थके हुए था: .Intersect(), .Except(), .Union()

यहाँ .Intersect()(टिप्पणी मेरा) के लिए बीसीएल (.NET 4.0 / 4.5) कार्यान्वयन विघटित है :

private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
  Set<TSource> set = new Set<TSource>(comparer);
  foreach (TSource source in second)                    // O(M)
    set.Add(source);                                    // O(1)

  foreach (TSource source in first)                     // O(N)
  {
    if (set.Remove(source))                             // O(1)
      yield return source;
  }
}

निष्कर्ष:

  • प्रदर्शन O (M + N) है
  • जब संग्रह पहले से सेट हैं , तो कार्यान्वयन लाभ नहीं उठाता है । (यह आवश्यक रूप से सीधा नहीं हो सकता है, क्योंकि उपयोग के लिए भी मेल खाना पड़ता है ।)IEqualityComparer<T>

पूर्णता के लिए, यहाँ के लिए कार्यान्वयन हैं .Union()और .Except()

स्पॉयलर अलर्ट: वे, भी, हे (एन + एम) जटिलता है।

private static IEnumerable<TSource> UnionIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
  Set<TSource> set = new Set<TSource>(comparer);
  foreach (TSource source in first)
  {
    if (set.Add(source))
      yield return source;
  }
  foreach (TSource source in second)
  {
    if (set.Add(source))
      yield return source;
  }
}


private static IEnumerable<TSource> ExceptIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
  Set<TSource> set = new Set<TSource>(comparer);
  foreach (TSource source in second)
    set.Add(source);
  foreach (TSource source in first)
  {
    if (set.Add(source))
      yield return source;
  }
}

8

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

समझाने के लिए, यहाँ Enumerable.CountSystem.Core से परिलक्षित स्रोत कोड (ILSpy के सौजन्य से) है :

// System.Linq.Enumerable
public static int Count<TSource>(this IEnumerable<TSource> source)
{
    checked
    {
        if (source == null)
        {
            throw Error.ArgumentNull("source");
        }
        ICollection<TSource> collection = source as ICollection<TSource>;
        if (collection != null)
        {
            return collection.Count;
        }
        ICollection collection2 = source as ICollection;
        if (collection2 != null)
        {
            return collection2.Count;
        }
        int num = 0;
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                num++;
            }
        }
        return num;
    }
}

जैसा कि आप देख सकते हैं, यह बस हर तत्व की गणना करने के भोले समाधान से बचने के लिए कुछ प्रयास करता है।


गणना प्राप्त करने के लिए पूरे ऑब्जेक्ट के माध्यम से पुनरावृत्ति करना () यदि यह एक IEnnumerable मेरे लिए बहुत अच्छा लगता है ...
Zonko

4
@Zonko: मैं आपकी बात नहीं समझता। मैंने अपने उत्तर में यह दिखाने के लिए संशोधन किया है कि Enumerable.Countजब तक कोई स्पष्ट विकल्प न हो, तब तक यह पुनरावृत्ति नहीं करता है। आपने इसे कम भोला कैसे बनाया होगा?
मार्सेलो कैंटोस

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

@MarceloCantos एरे को क्यों नहीं संभाला जाता है? यह ElementAtOrDefault विधि referenceource.microsoft.com/#System.Core/System/Linq/… के
Freshblood

@ जलप्रलय वे हैं। (Arrays ICollection को लागू करते हैं।) ElementAtOrDefault के बारे में नहीं जानते, हालाँकि। मैं अनुमान लगा रहा हूँ कि सरणियाँ ICollection <T> को भी लागू करती हैं, लेकिन इन दिनों मेरा .Net काफी जंग खा रहा है।
मार्सेलो कैंटोस

3

मैं सिर्फ रिफ्लेक्टर को तोड़ता हूं और वे अंतर्निहित प्रकार की जांच करते हैं जब Containsकहा जाता है।

public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value)
{
    ICollection<TSource> is2 = source as ICollection<TSource>;
    if (is2 != null)
    {
        return is2.Contains(value);
    }
    return source.Contains<TSource>(value, null);
}

3

सही उत्तर है "यह निर्भर करता है"। यह इस बात पर निर्भर करता है कि अंतर्निहित IEnumerable किस प्रकार का है। मुझे पता है कि कुछ संग्रह (जैसे संग्रह जो ICollection या IList को लागू करते हैं) के लिए विशेष कोडपाथ हैं जिनका उपयोग किया जाता है, हालांकि वास्तविक कार्यान्वयन कुछ विशेष करने की गारंटी नहीं है। उदाहरण के लिए मुझे पता है कि ElementAt () के पास इंडेक्सेबल संग्रह के लिए एक विशेष मामला है, इसी तरह काउंट () के साथ। लेकिन सामान्य तौर पर आपको संभवतः सबसे खराब स्थिति O (n) प्रदर्शन माननी चाहिए।

सामान्य तौर पर मुझे नहीं लगता कि आप जिस तरह की प्रदर्शन की गारंटी चाहते हैं, उसे खोजने जा रहे हैं, हालांकि यदि आप किसी विशेष प्रदर्शन समस्या में लिनक ऑपरेटर के साथ भाग लेते हैं, तो आप हमेशा अपने विशेष संग्रह के लिए इसे फिर से लागू कर सकते हैं। इसके अलावा कई ब्लॉग और एक्स्टेंसिबिलिटी प्रोजेक्ट हैं जो इस प्रकार के प्रदर्शन की गारंटी को जोड़ने के लिए Linq को ऑब्जेक्ट्स तक बढ़ाते हैं। अनुक्रमित LINQ की जांच करें जो अधिक प्रदर्शन लाभ के लिए ऑपरेटर सेट पर विस्तारित और जोड़ता है।

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