2012-01-24 20 views
6

का उपयोग करते समय ऑर्डरबी() को ऑप्टिमाइज़ करना तो मेरे पास काफी मानक LINQ-to-ऑब्जेक्ट सेटअप है।किसी भी()

var query = expensiveSrc.Where(x=> x.HasFoo) 
         .OrderBy(y => y.Bar.Count()) 
         .Select(z => z.FrobberName);  

// ... 

if (!condition && !query.Any()) 
return; // seems to enumerate and sort entire enumerable 

// ... 

foreach (var item in query) 
    // ... 

यह सब कुछ दो बार बताता है। जो बुरा है

var queryFiltered = expensiveSrc.Where(x=> x.HasFoo); 

var query = queryFiltered.OrderBy(y => y.Bar.Count()) 
         .Select(z => z.FrobberName); 

if (!condition && !queryFiltered.Any()) 
    return; 

// ... 

foreach (var item in query) 
    // ... 

काम करता है, लेकिन क्या कोई बेहतर तरीका है?

गैर-आवश्यक संचालन को बाईपास करने के लिए किसी भी() को "प्रबुद्ध" करने के लिए कोई गैर-पागल तरीका होगा? मुझे लगता है कि मुझे इस तरह के अनुकूलन EduLinq में जाना याद है।

उत्तर

2

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

static class QueryableHelper { 
    public static bool Any<T>(this IQueryable<T> source) { 
     var e = source.Expression; 
     while (e is MethodCallExpression) { 
      var mce = e as MethodCallExpression; 
      switch (mce.Method.Name) { 
       case "Select": 
       case "OrderBy": 
       case "ThenBy": break; 
       default: goto dun; 
      } 
      e = mce.Arguments.First(); 
     } 
     dun: 
     var d = Expression.Lambda<Func<IQueryable<T>>>(e).Compile(); 
     return Queryable.Any(d()); 
    } 
} 

क्वेरीज़ खुद को इस तरह संशोधित किया जाना चाहिए:

var query = expensiveSrc.AsQueryable() 
         .Where(x=> x.HasFoo) 
         .OrderBy(y => y.Bar.Count()) 
         .Select(z => z.FrobberName); 
9

क्यों न सिर्फ अनावश्यक से छुटकारा पाने:

if (!query.Any()) 
return; 

यह वास्तव में किसी भी उद्देश्य की सेवा कर नहीं लगता है - यहां तक ​​कि इसके बिना, foreach के शरीर पर अमल नहीं होगा, बशर्ते क्वेरी की पैदावार नहीं परिणाम है। तो Any() चेक इन के साथ, आप फास्ट पथ में कुछ भी नहीं बचाते हैं, और धीमे पथ में दो बार गणना करते हैं।

दूसरी ओर, यदि आप पता होना चाहिए अगर वहाँ थे कोई भी परिणाम के बाद पाया पाश के अंत में, आप के रूप में अच्छी सिर्फ एक ध्वज का उपयोग हो सकता है:

bool itemFound = false; 

foreach (var item in query) 
{ 
    itemFound = true; 
    ... // Rest of the loop body goes here. 
} 

if(itemFound) 
{ 
    // ... 
} 

या आप इस्तेमाल कर सकते हैं प्रगणक सीधे अगर आप पाश शरीर में अनावश्यक ध्वज स्थापित करने के बारे में वास्तव में चिंतित हैं:

using(var erator = query.GetEnumerator()) 
{ 
    bool itemFound = erator.MoveNext(); 

    if(itemFound) 
    { 
     do 
     { 
      // Do something with erator.Current; 
     } while(erator.MoveNext()) 
    } 

    // Do something with itemFound 
} 
+1

यह ठीक काम करता है, लेकिन केवल इसलिए कि उसके पास 'if' और 'foreach' के बीच कोई कोड नहीं था। – Gabe

+0

@Gabe: मुझे समझ में नहीं आता है। अगर और foreach के बीच कोई कोड नहीं है। हमें अन्यथा क्यों ग्रहण करना चाहिए? – Ani

+0

मैं स्मृति से पुनः टाइप कर रहा था, कोड वास्तव में 'if (condition && query.Any()) {/ * .. */foreach {..}/* .. * /} 'की तरह अधिक है। हां, मैं foreach को अनलॉक कर सकता हूं और इसे अधिक कुशलतापूर्वक (पठनीयता की कीमत पर) कर सकता हूं, लेकिन पहले आइटम को दो बार प्राप्त करना बुरा नहीं है (और यह बाध्य है)। हालांकि पूरे अनुक्रम ... मैं और जटिल परिस्थितियों के बारे में भी सोच रहा हूं जहां प्रश्न अलग-अलग लोगों द्वारा लिखे गए हैं या लिखे गए हैं, जहां केवल मध्यवर्ती मूल्य का उपयोग करना मुश्किल हो सकता है। – Fowl

1

इस प्रयास करें:

var items = expensiveSrc.Where(x=> x.HasFoo) 
         .OrderBy(y => y.Bar.Count()) 
         .Select(z => z.FrobberName).ToList(); 

// ... 

if (!condition && items.Count == 0) 
return; // Just check the count 

// ... 

foreach (var item in items) 
    // ... 

क्वेरी केवल एक बार निष्पादित की जाती है।

+0

लेकिन मैंने स्ट्रीमिंग/आलसी लोडिंग खो दी है जो कि लिनक का आधा बिंदु है। – Fowl

+1

लेकिन स्ट्रीमिंग और आलसी लोडिंग समाप्त होती है जब आप किसी भी() एक्सटेंशन विधि को निष्पादित करते हैं! यदि कोई है, तो आपको वैसे भी पूरी क्वेरी निष्पादित करने की आवश्यकता होगी। यदि नहीं, तो एक खाली गणना करने के लिए ऑर्डरबी करना सस्ता होगा। दरअसल, यही सवाल है कि लिंक के स्ट्रीमिंग/आलसी लोडिंग के कारण दो बार एक प्रश्न निष्पादित करने से बचें। – ivowiblo

1

लेकिन मैं स्ट्रीमिंग/आलसी लोड हो रहा है

भिन्न परिणामों के साथ

लेज़ी लोड हो रहा है (आस्थगित निष्पादन), और 2 LINQ प्रश्नों अनुकूलित नहीं किया जा सकता है LINQ के आधे बिंदु (है कि खो दिया है कम) 1 क्वेरी निष्पादन के लिए।

+0

यह सख्ती से सच है, मुझे लगता है। हालांकि 'किसी भी' की आवश्यकता नहीं है, और वास्तव में * पूरे * "क्वेरी निष्पादन" का नतीजा नहीं है। छोटी सी मामूली लागत सोचें, लेकिन बड़ी संख्या में गुणा करें। – Fowl

+0

किसी के पास मामूली लागत है (बड़ी संख्या से गुणा इसे बड़ा बनाता है)। यह सच है, और यही वह है जो आपने किया है जो मुझे लगता है कि सबसे अच्छा/एकमात्र तरीका है। आप बेहतर दिखने के लिए अपने कोड को दोबारा कर सकते हैं, लेकिन प्रदर्शन के अनुसार मैं बेहतर विकल्प के बारे में सोच नहीं सकता। Ivowiblo द्वारा उत्तर आवश्यक है (स्थगित निष्पादन खोने की कीमत पर)। लेकिन आप इससे सहमत नहीं हैं। – Tilak

+0

मुझे लगता है कि यह स्वीकार करना हमेशा मुश्किल होता है कि कब स्वीकार करना है "नहीं, कोई बेहतर तरीका नहीं है (बाधाओं के भीतर)"। हो सकता है कि एक दिन linq-to-ऑब्जेक्ट्स में एक क्वेरी ऑप्टिमाइज़र होगा? ;) – Fowl

2

संपादित करें (संशोधित): यह उत्तर दो बार निष्पादित क्वेरी के मुद्दे को संबोधित करता है, जो मुझे विश्वास है कि यह मुख्य मुद्दा है। नीचे देखें:

बनाना Any() स्मार्ट कुछ ऐसा है जो केवल लिंकक कार्यान्वयन कर सकता है, आईएमओ ... या यह प्रतिबिंब का उपयोग करके कुछ गंदा साहसिक होगा।

एक वर्ग का प्रयोग के रूप में नीचे दिखाया गया है, आप मूल गणनीय के उत्पादन में कैश कर सकते, और यह दो बार प्रगणित किया:

public class CachedEnumerable<T> 
{ 
    public CachedEnumerable(IEnumerable<T> enumerable) 
    { 
     _source = enumerable.GetEnumerator(); 
    } 

    public IEnumerable<T> Enumerate() 
    { 
     int itemIndex = 0; 
     while (true) 
     { 
      if (itemIndex < _cache.Count) 
      { 
       yield return _cache[itemIndex]; 
       itemIndex++; 
       continue; 
      } 

      if (!_source.MoveNext()) 
       yield break; 

      var current = _source.Current; 
      _cache.Add(current); 
      yield return current; 
      itemIndex++;     
     } 

    } 

    private List<T> _cache = new List<T>(); 
    private IEnumerator<T> _source; 
} 

इस तरह आप LINQ के आलसी पहलू रखने के लिए, कोड पठनीय रखने के लिए और सामान्य। यह धीमा हो जाएगा कि सीधे IEnumerator<> का उपयोग करें। इस वर्ग को विस्तारित करने और अनुकूलित करने के कई अवसर हैं, जैसे पुरानी वस्तुओं को हटाने, कोरआउट आदि से छुटकारा पाने के लिए नीति। लेकिन यह मुझे लगता है कि इस सवाल के बिंदु से परे है।

ओह, और कक्षा अब थ्रेड सुरक्षित नहीं है। यह नहीं पूछा गया था, लेकिन मैं कल्पना कर रहा हूं कि लोग कोशिश कर रहे हैं। मुझे लगता है कि यह आसानी से जोड़ा जा सकता है, अगर स्रोत संख्यात्मक कोई धागा संबंध नहीं है ..

यह इष्टतम क्यों होगा?

चलो दो possibilites पर विचार करें: गणना तत्वों को दूषित कर सकता है या नहीं।

  • यदि इसमें तत्व हैं, तो यह दृष्टिकोण इष्टतम है क्योंकि क्वेरी केवल एक बार चलती है।
  • यदि इसमें कोई तत्व नहीं है, तो आप ऑर्डरबी को खत्म करने और अपने प्रश्नों का चयन करने के लिए परीक्षा देंगे, क्योंकि वे कोई मूल्य नहीं जोड़ते हैं। लेकिन .. Where() खंड के बाद शून्य आइटम हैं, तो सॉर्ट करने के लिए शून्य आइटम हैं, जो शून्य समय (अच्छी तरह से, लगभग) खर्च होंगे। Select() खंड के लिए भी यही है।

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

2

वहाँ किसी भी() गैर आवश्यक कार्रवाई बायपास करने के लिए किसी भी गैर पागल रास्ता "प्रबुद्ध" करने के लिए होगा? मुझे लगता है कि मुझे इस तरह के अनुकूलन EduLinq में जाना याद है।

खैर मैं किसी भी सवाल जो Edulinq :)

का उल्लेख है इस मामले में अनदेखी करने के लिए नहीं जा रहा हूँ, Edulinq अच्छी तरह से, LINQ से वस्तुओं के लिए तेजी से हो सकता है के रूप में अपनी OrderBy कार्यान्वयन के रूप में आलसी है के रूप में यह हो सकता है - यह केवल उन तत्वों को पुनर्प्राप्त करने के लिए जितना आवश्यक है उतना ही आवश्यक है।

हालांकि, मूल रूप से यह अभी भी को कुछ भी वापस करने से पहले पूरे अनुक्रम को पढ़ना है। आखिरकार, अनुक्रम में अंतिम तत्व पहले व्यक्ति हो सकता है जिसे वापस किया जाना है।

आप पूरे ढेर के नियंत्रण में हैं, तो आप Any() का पता लगाने कि यह अपने "ज्ञात" IOrderedEnumerable कार्यान्वयन पर बुलाया जा रहा है, और मूल स्रोत के लिए सीधे जा सकते हैं।ध्यान दें कि यह देखे गए व्यवहार में परिवर्तन बनाता है - अगर पूरे अनुक्रम पर पुनरावृत्ति एक अपवाद फेंकता है (या कोई अन्य दुष्प्रभाव है) तो उस दुष्प्रभाव अनुकूलन द्वारा खो जाएगा। आप तर्क दे सकते हैं कि ठीक है, निश्चित रूप से - LINQ में "मान्य" अनुकूलन के रूप में क्या मायने रखता है एक निश्चित रूप से मुश्किल क्षेत्र है।

एक अन्य संभावना जो बहुत भयानक है लेकिन जो इस विशेष समस्या को हल करेगी, यह इटरेटर IOrderedEnumerable से वापस लौटाएगी, बस स्रोत से MoveNext() का पहला मान लें। Any के सामान्य कार्यान्वयन के लिए यह पर्याप्त है, और उस समय हम को यह जानने की आवश्यकता नहीं है कि पहला तत्व क्या है। हम पहली बार Current संपत्ति का उपयोग होने तक वास्तविक सॉर्टिंग को रोक सकते हैं।

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

0

तुम क्यों, एक .ToArray()

var query = expensiveSrc.Where(x=> x.HasFoo) 
        .OrderBy(y => y.Bar.Count()) 
        .Select(z => z.FrobberName).ToArray();  

उपयोग कर रहे हैं नहीं करता है, तो वहाँ नहीं तत्व हैं छंटाई और चयन ज्यादा भूमि के ऊपर नहीं देना चाहिए। यदि आप सॉर्ट कर रहे हैं, तो आपको किसी भी प्रकार की कैश की आवश्यकता है जहां डेटा स्टोर करना है, तो ओवरहेड। ToArray उत्पादन इतना नहीं होना चाहिए। यदि आप OrderedEnumerable क्लास को डीकंपाइल करते हैं, तो आप पाते हैं कि संदर्भों वाला एक int [] सरणी बनती है, इसलिए आप केवल एक नया संदर्भ सरणी का उपयोग करके .oArray (या .ToList) का उपयोग कर बनाते हैं।

लेकिन यदि महंगा एसआरसी डेटाबेस से आता है, तो अन्य रणनीतियां बेहतर हो सकती हैं। यदि डेटाबेस में ऑर्डरिंग किया जा सकता है, तो यह आपको बहुत अधिक ओवरहेड देगा क्योंकि डेटा दो बार संग्रहीत किया जाता है।

संबंधित मुद्दे