क्यों और कहाँ का चयन कर रहे हैं बेहतर प्रदर्शन?


145

मेरे पास एक वर्ग है, जैसे:

public class MyClass
{
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

वास्तव में यह बहुत बड़ा है, लेकिन यह समस्या (अजीबता) को फिर से बनाता है।

मैं उस राशि को प्राप्त करना चाहता हूं Value, जहां उदाहरण मान्य है। अब तक, मुझे इसके दो समाधान मिल गए हैं।

पहला यह है:

int result = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();

दूसरा, हालांकि, यह है:

int result = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();

मैं सबसे कुशल विधि प्राप्त करना चाहता हूं। मैंने, पहले तो सोचा था कि दूसरा कुशल होगा। फिर मेरे बारे में सैद्धांतिक भाग "ठीक है, एक ओ (एन + एम + एम) है, दूसरा एक ओ (एन + एन) है। पहले वाले को और अधिक इनवैलिड के साथ बेहतर प्रदर्शन करना चाहिए, जबकि दूसरे को बेहतर प्रदर्शन करना चाहिए।" कम के साथ"। मैंने सोचा था कि वे समान प्रदर्शन करेंगे। संपादित करें: और फिर @ मर्टिन ने बताया कि कहां और चयन संयुक्त थे, इसलिए यह वास्तव में ओ (एम + एन) होना चाहिए। हालांकि, यदि आप नीचे देखते हैं, तो ऐसा लगता है कि यह संबंधित नहीं है।


इसलिए मैंने इसे परीक्षण के लिए रखा।

(यह 100+ लाइनें है, इसलिए मुझे लगा कि इसे जिस्ट के रूप में पोस्ट करना बेहतर है।)
परिणाम दिलचस्प थे।

0% टाई टॉलरेंस के साथ:

तराजू लगभग 30 बिंदुओं द्वारा Selectऔर के पक्ष में हैं Where

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where + Select: 65
Select: 36

2% टाई सहिष्णुता के साथ:

यह वही है, सिवाय इसके कि कुछ के लिए वे 2% के भीतर थे। मैं कहता हूँ कि त्रुटि का एक न्यूनतम मार्जिन है। Selectऔर Whereअब सिर्फ ~ 20 पॉइंट लीड है।

How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 6
Where + Select: 58
Select: 37

5% टाई सहिष्णुता के साथ:

यह वही है जो मैं कहूंगा कि मेरी त्रुटि अधिकतम मार्जिन है। यह इसे थोड़ा बेहतर बनाता है Select, लेकिन ज्यादा नहीं।

How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 17
Where + Select: 53
Select: 31

10% टाई सहिष्णुता के साथ:

यह मेरी त्रुटि के मार्जिन से बाहर है, लेकिन मैं अभी भी परिणाम में दिलचस्पी रखता हूं। क्योंकि यह Selectऔर देता है और Whereबीस अंक की सीसा यह कुछ समय के लिए था।

How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 36
Where + Select: 44
Select: 21

25% टाई के साथ सहिष्णुता:

यह मेरी गलती के मार्जिन से बाहर निकलने का रास्ता है, लेकिन मुझे अभी भी परिणाम में दिलचस्पी है, क्योंकि अभी भी ( Selectऔर लगभग) अपनी 20 अंक की बढ़त बनाए हुए हैं। ऐसा लगता है जैसे यह एक अलग में इसे outclassing है, और यही इसे नेतृत्व दे रहा है।Where

How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where + Select: 16
Select: 0


अब, मेरा अनुमान है कि है कि 20 अंकों की बढ़त के बीच है, जहां वे दोनों पाने के लिए बाध्य कर रहे हैं से आया है चारों ओर एक ही प्रदर्शन। मैं कोशिश कर सकता था और इसे लॉग इन कर सकता था, लेकिन इसमें जानकारी लेने के लिए पूरी जानकारी होगी। एक ग्राफ बेहतर होगा, मुझे लगता है।

तो मैंने यही किया।

बनाम सेलेक्ट और कहां सेलेक्ट करें।

यह दर्शाता है कि Selectरेखा स्थिर (अपेक्षित) रहती है और यह Select + Whereरेखा ऊपर चढ़ जाती है (अपेक्षित)। हालांकि, कारण है कि यह साथ पूरा नहीं करता है क्या पहेली मुझे है Selectवास्तव में मैं 50 से पहले उम्मीद कर रहा था, के रूप में एक अतिरिक्त प्रगणक के लिए बनाया जा सकता था: 50 पर या उससे पहले Selectऔर Where। मेरा मतलब है, यह 20-पॉइंट लीड दिखाता है, लेकिन यह स्पष्ट नहीं करता है कि क्यों। यह, मुझे लगता है, मेरे प्रश्न का मुख्य बिंदु है।

ऐसा व्यवहार क्यों करता है? क्या मुझे इस पर भरोसा करना चाहिए? यदि नहीं, तो क्या मुझे दूसरे का उपयोग करना चाहिए या इस एक का?


जैसा कि @KingKong ने टिप्पणियों में उल्लेख किया है, आप लैंबडा लेने वाले Sumअधिभार का भी उपयोग कर सकते हैं । इसलिए मेरे दो विकल्प अब इसमें बदल दिए गए हैं:

प्रथम:

int result = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);

दूसरा:

int result = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

मैं इसे थोड़ा छोटा करने जा रहा हूं, लेकिन:

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where: 60
Sum: 41
How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 8
Where: 55
Sum: 38
How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 21
Where: 49
Sum: 31
How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 39
Where: 41
Sum: 21
How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where: 16
Sum: 0

बीस-सूत्रीय लीड अभी भी है, जिसका अर्थ है कि टिप्पणियों में @Marcin द्वारा इंगित Whereऔर Selectसंयोजन के साथ ऐसा नहीं करना है ।

पाठ की मेरी दीवार के माध्यम से पढ़ने के लिए धन्यवाद! इसके अलावा, यदि आप रुचि रखते हैं, तो यहां संशोधित संस्करण है जो एक्सेल में लगने वाले सीएसवी को लॉग करता है।


1
मैं कहूंगा कि यह इस बात पर निर्भर करता है कि राशि और पहुंच कितनी महंगी है mc.Value
मेदिनेक

14
@ It'sNotALie। Where+ Selectइनपुट संग्रह पर दो अलग-अलग पुनरावृत्तियों का कारण नहीं बनता है। LINQ to Objects इसे एक iteration में ऑप्टिमाइज़ करते हैं। मेरे ब्लॉग पोस्ट
MarcinJuraszek

4
दिलचस्प। मुझे केवल इस बात पर ध्यान दें कि एक सरणी पर लूप के लिए सबसे अच्छा LINQ समाधान की तुलना में 10x तेज होगा। तो अगर आप शिकार के लिए जाते हैं, तो पहले स्थान पर LINQ का उपयोग न करें।
यूएसआर

2
कभी-कभी लोग वास्तविक शोध के बाद पूछते हैं, यह एक उदाहरण प्रश्न है: मैं सी # नहीं हूं उपयोगकर्ता हॉट-प्रश्न-सूची से आया था।
बृजेश चौहान

2
@WiSaGaN यह एक अच्छी बात है। हालांकि, अगर यह शाखा बनाम सशर्त कदम के कारण है, तो हम 50% / 50% पर सबसे नाटकीय अंतर देखने की उम्मीद करेंगे। यहां, हम सिरों पर सबसे नाटकीय अंतर देखते हैं, जहां शाखाकरण सबसे अधिक अनुमानित है। जहां एक शाखा है, और त्रिगुट एक सशर्त चाल है, तो हम उम्मीद करेंगे कि सभी तत्वों के वैध होने पर वापस आने का समय कहां होगा, लेकिन यह कभी वापस नहीं आता है।
जॉन Tseng

जवाबों:


131

Selectपूरे सेट पर एक बार पुनरावृत्त होता है और, प्रत्येक आइटम के लिए, एक सशर्त शाखा (वैधता की जाँच) और एक +ऑपरेशन करता है।

Where+Selectएक पुनरावृत्त बनाता है जो अमान्य तत्वों को छोड़ देता है ( yieldउन्हें नहीं ), +केवल वैध वस्तुओं पर प्रदर्शन करता है।

तो, एक के लिए लागत Selectहै:

t(s) = n * ( cost(check valid) + cost(+) )

और इसके लिए Where+Select:

t(ws) = n * ( cost(check valid) + p(valid) * (cost(yield) + cost(+)) )

कहाँ पे:

  • p(valid) संभावना है कि सूची में कोई आइटम मान्य है।
  • cost(check valid) उस शाखा की लागत है जो वैधता की जाँच करता है
  • cost(yield)whereइट्रेटर के नए राज्य के निर्माण की लागत है, जो सरल इटरेटर की तुलना में अधिक जटिल है जो Selectसंस्करण का उपयोग करता है।

जैसा कि आप देख सकते हैं, किसी दिए गए के लिए n, Selectसंस्करण एक स्थिर है, जबकि Where+Selectसंस्करण p(valid)एक चर के रूप में एक रैखिक समीकरण है । लागतों का वास्तविक मान दो पंक्तियों के प्रतिच्छेदन बिंदु को निर्धारित करता है, और चूँकि वे cost(yield)भिन्न हो सकते हैं cost(+), इसलिए वे आवश्यक रूप से p(valid)= 0.5 पर प्रतिच्छेद नहीं करते हैं ।


34
केवल एकमात्र उत्तर होने के लिए (अब तक) जो वास्तव में प्रश्न को संबोधित करता है, उत्तर का अनुमान नहीं लगाता है और केवल "मुझे भी" उत्पन्न नहीं करता है! आंकड़े।
बाइनरी वॉयर

4
तकनीकी रूप से LINQ पद्धतियों में "सेट" के बजाय एक बार पूरे संग्रह में चलाए जाने वाले अभिव्यक्ति पेड़ बनते हैं।
1336

क्या है cost(append)? हालांकि वास्तव में अच्छा जवाब, इसे केवल आंकड़ों के बजाय एक अलग कोण से देखता है।
इट्नोटली।

5
Whereकुछ भी नहीं बनाता है, बस एक तत्व को sourceअनुक्रम से समय पर लौटाता है यदि केवल यह विधेय भरता है।
MarcinJuraszek 16

13
@ साइकाइक - अभिव्यक्त पेड़ यहां प्रासंगिक नहीं हैं, क्योंकि यह लिनक -टू-ऑब्जेक्ट्स है , न कि लिन्क-टू-समथिंग-अदर (एंटिटी, उदाहरण के लिए)। यही कारण है कि बीच का अंतर है IEnumerable.Select(IEnumerable, Func)और IQueryable.Select(IQueryable, Expression<Func>)। आप सही हैं कि LINQ "कुछ भी नहीं" करता है जब तक आप संग्रह पर पुनरावृति नहीं करते हैं, जो कि शायद आपका मतलब है।
कोबी

33

समय-अंतर के कारण क्या है, इसकी गहन व्याख्या यहां दी गई है।


इस तरह दिखता है Sum()समारोह IEnumerable<int>:

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;
    foreach(int item in source)
    {
        sum += item;
    }
    return sum;
}

C # में,। foreachIt के संस्करण के लिए। बस के लिए सिंटैक्टिक शुगर है, (साथ भ्रमित नहीं होना ) । इसलिए उपरोक्त कोड वास्तव में इसके लिए अनुवादित है:IEnumerator<T> IEnumerable<T>

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;

    IEnumerator<int> iterator = source.GetEnumerator();
    while(iterator.MoveNext())
    {
        int item = iterator.Current;
        sum += item;
    }
    return sum;
}

याद रखें, आप जिस कोड की तुलना कर रहे हैं उसकी दो पंक्तियाँ निम्नलिखित हैं

int result1 = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);
int result2 = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

अब यहाँ किकर है:

LINQ आस्थगित निष्पादन का उपयोग करता है । इस प्रकार, जबकि यह प्रतीत हो सकता है कि result1संग्रह पर दो बार पुनरावृत्त होता है, यह वास्तव में केवल एक बार इस पर पुनरावृत्ति करता है। यह Where()शर्त वास्तव Sum()में कॉल के अंदर, MoveNext() (यह जादू के लिए संभव है धन्यवाद yield return) के दौरान लागू की जाती है

इसका मतलब यह है कि, लूप result1के अंदर का कोड while,

{
    int item = iterator.Current;
    sum += item;
}

केवल प्रत्येक आइटम के लिए एक बार निष्पादित किया जाता है mc.IsValid == true। तुलना करके, संग्रह में प्रत्येक आइटम result2के लिए उस कोड को निष्पादित करेगा । यही कारण है कि आम तौर पर तेज है।result1

(हालांकि, ध्यान दें कि अभी भी कॉल करने की Where()स्थिति MoveNext()में कुछ छोटे ओवरहेड हैं, इसलिए यदि अधिकांश / सभी वस्तुओं में वास्तव में तेजी है mc.IsValid == true, तो result2!)


उम्मीद है कि अब यह स्पष्ट है कि result2आमतौर पर धीमी क्यों है। अब मैं यह बताना चाहता हूं कि मैंने टिप्पणियों में क्यों कहा कि ये LINQ प्रदर्शन तुलनाएं मायने नहीं रखती हैं

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

तो, आपके LINQ वर्कफ़्लो को इस तरह दिखना चाहिए:

  1. हर जगह LINQ का उपयोग करें।
  2. प्रोफाइल।
  3. यदि प्रोफाइलर कहता है कि LINQ एक अड़चन का कारण है, तो LINQ के बिना कोड के उस टुकड़े को फिर से लिखना।

सौभाग्य से, LINQ बाधाएं दुर्लभ हैं। हेक, अड़चन दुर्लभ हैं। मैंने पिछले कुछ वर्षों में सैकड़ों LINQ स्टेटमेंट लिखे हैं, और <1% की जगह समाप्त कर दिया है। और के सबसे उन की वजह से थे LINQ2EF बल्कि LINQ की गलती होने से, के गरीब एसक्यूएल अनुकूलन।

इसलिए, हमेशा की तरह, पहले स्पष्ट और समझदार कोड लिखें, और जब तक आप माइक्रो-ऑप्टिमाइज़ेशन के बारे में चिंता करने के लिए तैयार नहीं हो जाते तब तक प्रतीक्षा करें ।


3
छोटा परिशिष्ट: शीर्ष उत्तर तय किया गया है।
इट्नोटली।

16

हास्यास्पद चीज़। क्या आप जानते हैं कि Sum(this IEnumerable<TSource> source, Func<TSource, int> selector)परिभाषित कैसे किया जाता है? यह Selectविधि का उपयोग करता है!

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select(selector).Sum();
}

तो वास्तव में, यह सब लगभग एक ही काम करना चाहिए। मैंने अपने दम पर त्वरित शोध किया, और यहाँ परिणाम हैं:

Where -- mod: 1 result: 0, time: 371 ms
WhereSelect -- mod: 1  result: 0, time: 356 ms
Select -- mod: 1  result 0, time: 366 ms
Sum -- mod: 1  result: 0, time: 363 ms
-------------
Where -- mod: 2 result: 4999999, time: 469 ms
WhereSelect -- mod: 2  result: 4999999, time: 429 ms
Select -- mod: 2  result 4999999, time: 362 ms
Sum -- mod: 2  result: 4999999, time: 358 ms
-------------
Where -- mod: 3 result: 9999999, time: 441 ms
WhereSelect -- mod: 3  result: 9999999, time: 452 ms
Select -- mod: 3  result 9999999, time: 371 ms
Sum -- mod: 3  result: 9999999, time: 380 ms
-------------
Where -- mod: 4 result: 7500000, time: 571 ms
WhereSelect -- mod: 4  result: 7500000, time: 501 ms
Select -- mod: 4  result 7500000, time: 406 ms
Sum -- mod: 4  result: 7500000, time: 397 ms
-------------
Where -- mod: 5 result: 7999999, time: 490 ms
WhereSelect -- mod: 5  result: 7999999, time: 477 ms
Select -- mod: 5  result 7999999, time: 397 ms
Sum -- mod: 5  result: 7999999, time: 394 ms
-------------
Where -- mod: 6 result: 9999999, time: 488 ms
WhereSelect -- mod: 6  result: 9999999, time: 480 ms
Select -- mod: 6  result 9999999, time: 391 ms
Sum -- mod: 6  result: 9999999, time: 387 ms
-------------
Where -- mod: 7 result: 8571428, time: 489 ms
WhereSelect -- mod: 7  result: 8571428, time: 486 ms
Select -- mod: 7  result 8571428, time: 384 ms
Sum -- mod: 7  result: 8571428, time: 381 ms
-------------
Where -- mod: 8 result: 8749999, time: 494 ms
WhereSelect -- mod: 8  result: 8749999, time: 488 ms
Select -- mod: 8  result 8749999, time: 386 ms
Sum -- mod: 8  result: 8749999, time: 373 ms
-------------
Where -- mod: 9 result: 9999999, time: 497 ms
WhereSelect -- mod: 9  result: 9999999, time: 494 ms
Select -- mod: 9  result 9999999, time: 386 ms
Sum -- mod: 9  result: 9999999, time: 371 ms

निम्नलिखित कार्यान्वयन के लिए:

result = source.Where(x => x.IsValid).Sum(x => x.Value);
result = source.Select(x => x.IsValid ? x.Value : 0).Sum();
result = source.Sum(x => x.IsValid ? x.Value : 0);
result = source.Where(x => x.IsValid).Select(x => x.Value).Sum();

modका अर्थ है: हर 1 modआइटम से अमान्य है: mod == 1प्रत्येक आइटम के लिए अमान्य है, के लिए mod == 2विषम आइटम अमान्य हैं, आदि संग्रह में 10000000आइटम हैं।

यहाँ छवि विवरण दर्ज करें

और 100000000आइटम के साथ संग्रह के लिए परिणाम :

यहाँ छवि विवरण दर्ज करें

जैसा कि आप देख सकते हैं, Selectऔर Sumपरिणाम सभी modमूल्यों में काफी सुसंगत हैं। हालाँकि whereऔर where+ selectनहीं हैं।


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

6

मेरा अनुमान है कि वह संस्करण जहाँ 0 से फ़िल्टर किया जाता है और वे सम के लिए एक विषय नहीं हैं (यानी आप जोड़ को अंजाम नहीं दे रहे हैं)। यह निश्चित रूप से एक अनुमान है क्योंकि मैं यह स्पष्ट नहीं कर सकता कि अतिरिक्त लैम्ब्डा अभिव्यक्ति को निष्पादित करने और कई तरीकों को कॉल करने से 0 का एक साधारण जोड़ आउटपरफॉर्म कैसे होता है।

मेरे एक दोस्त ने सुझाव दिया कि तथ्य यह है कि राशि में 0 अतिप्रवाह चेक के कारण गंभीर प्रदर्शन जुर्माना हो सकता है। यह देखना दिलचस्प होगा कि यह अनियंत्रित संदर्भ में कैसा प्रदर्शन करेगा।


के साथ कुछ परीक्षण uncheckedयह एक छोटे से छोटे के लिए बेहतर बनाता है Select
इट्नोटली।

क्या कोई कह सकता है कि अनियंत्रित होने से स्टैक के नीचे या केवल शीर्ष स्तर के संचालन के तरीकों को प्रभावित किया जाता है?
स्टिलगर

1
@ स्टिलगर यह केवल शीर्ष स्तर पर लागू होता है।
ब्रांको दिमित्रिजेविक

इसलिए शायद हमें अनियंत्रित योग को लागू करने की जरूरत है और इसे इस तरह से आजमाना चाहिए।
स्टिलगर

5

निम्नलिखित नमूने को चलाने पर, यह मेरे लिए स्पष्ट हो जाता है कि सूची में संभावित वस्तुओं का एक ही समय जहां + चयन आउटपरफॉर्म कर सकता है, वास्तव में यह तब है जब यह अच्छी मात्रा (लगभग मेरे अनौपचारिक परीक्षणों में आधा) को छोड़ रहा हो। नीचे दिए गए छोटे से उदाहरण में, मुझे दोनों नमूनों में से लगभग समान संख्याएँ मिलती हैं जब Skips 10mil में से 4mil आइटम को लगभग छोड़ देता है। मैं रिलीज में भाग गया, और जहां + चयन करें वही परिणामों के साथ चयन का निष्पादन फिर से शुरू किया।

static void Main(string[] args)
        {
            int total = 10000000;
            Random r = new Random();
            var list = Enumerable.Range(0, total).Select(i => r.Next(0, 5)).ToList();
            for (int i = 0; i < 4000000; i++)
                list[i] = 10;

            var sw = new Stopwatch();
            sw.Start();

            int sum = 0;

            sum = list.Where(i => i < 10).Select(i => i).Sum();            

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            sum = list.Select(i => i).Sum();            

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
        }

ऐसा नहीं हो सकता क्योंकि आप अंडर-टेन को नहीं छोड़ते Select?
इट्नोटली।

3
डिबग में दौड़ना बेकार है।
MarcinJuraszek

1
@MarcinJuraszek जाहिर है। वास्तव में कहने का मतलब है कि मैं रिलीज में भाग गया :)
डेविड एनएन

@ यह बात नहीं है। यह मुझे लगता है कि जहां + चयन आउटपरफॉर्म कर सकता है वही एकमात्र तरीका है जहां बड़ी मात्रा में आइटम को फ़िल्टर किया जा रहा है।
डेविड एनएन

2
यह मूल रूप से मेरे प्रश्न का मंचन है। वे लगभग 60% पर टाई करते हैं, जैसे यह नमूना करता है। सवाल यह है कि, जिसका उत्तर यहां नहीं दिया गया है।
इट्नोटली।

4

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

यहां मुझे 10% तत्व अवैध मिले हैं

Where + Select + Sum:   257
Select + Sum:           253
foreach:                111
for:                    61

और 90% अवैध तत्वों के साथ:

Where + Select + Sum:   177
Select + Sum:           247
foreach:                105
for:                    58

और यहाँ मेरा बेंचमार्क कोड है ...

public class MyClass {
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

class Program {

    static void Main(string[] args) {

        const int count = 10000000;
        const int percentageInvalid = 90;

        var rnd = new Random();
        var myCollection = new List<MyClass>(count);
        for (int i = 0; i < count; ++i) {
            myCollection.Add(
                new MyClass {
                    Value = rnd.Next(0, 50),
                    IsValid = rnd.Next(0, 100) > percentageInvalid
                }
            );
        }

        var sw = new Stopwatch();
        sw.Restart();
        int result1 = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();
        sw.Stop();
        Console.WriteLine("Where + Select + Sum:\t{0}", sw.ElapsedMilliseconds);

        sw.Restart();
        int result2 = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();
        sw.Stop();
        Console.WriteLine("Select + Sum:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result2);

        sw.Restart();
        int result3 = 0;
        foreach (var mc in myCollection) {
            if (mc.IsValid)
                result3 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("foreach:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result3);

        sw.Restart();
        int result4 = 0;
        for (int i = 0; i < myCollection.Count; ++i) {
            var mc = myCollection[i];
            if (mc.IsValid)
                result4 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("for:\t\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result4);

    }

}

BTW, मैं स्टिल्गर के अनुमान से सहमत हूं : आपके दो मामलों की सापेक्ष गति अमान्य वस्तुओं के प्रतिशत के आधार पर भिन्न होती है, बस इसलिए कि नौकरी की मात्रा Sumको "जहां" मामले में भिन्न होना चाहिए।


1

वर्णन के माध्यम से समझाने की कोशिश करने के बजाय, मैं एक अधिक गणितीय दृष्टिकोण लेने जा रहा हूं।

नीचे दिए गए कोड को देखते हुए कि LINQ आंतरिक रूप से क्या कर रहा है, इसकी अनुमानित लागत इस प्रकार है:
केवल चयन करें: Nd + Na
कहां + चयन करें:Nd + Md + Ma

उस बिंदु का पता लगाने के लिए जहां वे पार करेंगे, हमें थोड़ा बीजगणित करने की आवश्यकता है:
Nd + Md + Ma = Nd + Na => M(d + a) = Na => (M/N) = a/(d+a)

इसका मतलब यह है कि विभक्ति बिंदु 50% पर होने के लिए, एक प्रतिनिधि मंगलाचरण की लागत लगभग एक अतिरिक्त लागत के समान होनी चाहिए। चूंकि हम जानते हैं कि वास्तविक विभक्ति बिंदु लगभग 60% था, इसलिए हम पीछे की ओर काम कर सकते हैं और यह निर्धारित कर सकते हैं कि @ ItNotALie के लिए एक प्रतिनिधि मंगलाचरण की लागत वास्तव में लगभग 2/3 थी, इसके अतिरिक्त लागत आश्चर्यजनक है, लेकिन यही है उसके नंबर कहते हैं।

static void Main(string[] args)
{
    var set = Enumerable.Range(1, 10000000)
                        .Select(i => new MyClass {Value = i, IsValid = i%2 == 0})
                        .ToList();

    Func<MyClass, int> select = i => i.IsValid ? i.Value : 0;
    Console.WriteLine(
        Sum(                        // Cost: N additions
            Select(set, select)));  // Cost: N delegate
    // Total cost: N * (delegate + addition) = Nd + Na

    Func<MyClass, bool> where = i => i.IsValid;
    Func<MyClass, int> wSelect = i => i.Value;
    Console.WriteLine(
        Sum(                        // Cost: M additions
            Select(                 // Cost: M delegate
                Where(set, where),  // Cost: N delegate
                wSelect)));
    // Total cost: N * delegate + M * (delegate + addition) = Nd + Md + Ma
}

// Cost: N delegate calls
static IEnumerable<T> Where<T>(IEnumerable<T> set, Func<T, bool> predicate)
{
    foreach (var mc in set)
    {
        if (predicate(mc))
        {
            yield return mc;
        }
    }
}

// Cost: N delegate calls
static IEnumerable<int> Select<T>(IEnumerable<T> set, Func<T, int> selector)
{
    foreach (var mc in set)
    {
        yield return selector(mc);
    }
}

// Cost: N additions
static int Sum(IEnumerable<int> set)
{
    unchecked
    {
        var sum = 0;
        foreach (var i in set)
        {
            sum += i;
        }

        return sum;
    }
}

0

मुझे लगता है कि यह दिलचस्प है कि MarcinJuraszek का परिणाम ItNotALie's से अलग है। विशेष रूप से, MarcinJuraszek के परिणाम एक ही स्थान पर सभी चार कार्यान्वयनों के साथ शुरू होते हैं, जबकि ItNotALie के परिणाम बीच में से पार हो जाते हैं। मैं बताऊंगा कि यह स्रोत से कैसे काम करता है।

आइए हम मान लें कि nकुल तत्व हैं, और mमान्य तत्व हैं।

Sumसमारोह बहुत सरल है। यह सिर्फ एन्यूमरेटर के माध्यम से लूप करता है: http://typedescriptor.net/browse/members/367300-System.Linq.Enumerable.Sum(IEnumerable%601)

सरलता के लिए, मान लें कि संग्रह एक सूची है। Select और WhereSelect दोनों एक बनाएंगे WhereSelectListIterator। इसका मतलब यह है कि उत्पन्न वास्तविक पुनरावृत्तियों समान हैं। दोनों ही मामलों में, वहाँ एक है Sumकि, पुनरावर्तक से अधिक लूप WhereSelectListIteratorपुनरावृत्ति का सबसे दिलचस्प हिस्सा MoveNext विधि है।

चूंकि चलने वाले समान हैं, लूप समान हैं। एकमात्र अंतर छोरों के शरीर में है।

इन मेमनों के शरीर की कीमत काफी समान है। जहाँ खण्ड एक क्षेत्र मान लौटाता है, और तिर्यक विधेय भी क्षेत्र मान लौटाता है। चयन खंड एक फ़ील्ड मान लौटाता है, और टर्नरी ऑपरेटर की दो शाखाएँ या तो फ़ील्ड मान या स्थिर लौटाती हैं। संयुक्त चयन खंड में एक टर्नरी ऑपरेटर के रूप में शाखा होती है, लेकिन व्हेयरसेल में शाखा का उपयोग करता है MoveNext

हालाँकि, ये सभी ऑपरेशन काफी सस्ते हैं। अब तक का सबसे महंगा ऑपरेशन शाखा है, जहां एक गलत भविष्यवाणी हमें खर्च करेगी।

यहां एक और महंगा ऑपरेशन है Invoke। एक फ़ंक्शन को जोड़ना मूल्य जोड़ने की तुलना में काफी अधिक समय लेता है, जैसा कि ब्रांको दिमित्रिजेविक ने दिखाया है।

इसके अलावा वजन में जाँच संचित है Sum। यदि प्रोसेसर के पास एक अंकगणितीय अतिप्रवाह झंडा नहीं है, तो यह भी महंगा हो सकता है।

इसलिए, दिलचस्प लागत हैं:

  1. ( n+ m) * चालान + m*checked+=
  2. n* चालान करें n*checked+=

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

यह MarcinJuraszek की प्रणाली पर दिखता है, जाँच + = में नगण्य लागत है, लेकिन ItNotALie's और Branko Dimitrijevic के सिस्टम पर, जाँच + की महत्वपूर्ण लागत है। ऐसा लगता है कि यह इटानोटली के सिस्टम पर सबसे महंगा है क्योंकि ब्रेक यहां तक ​​कि बिंदु बहुत अधिक है। ऐसा नहीं लगता है कि किसी ने सिस्टम से परिणाम पोस्ट किए हैं जहां संचय की लागत इनवोक की तुलना में बहुत अधिक है।


@ It'sNotALie। मुझे नहीं लगता कि किसी के पास गलत परिणाम है। मैं अभी कुछ चीजों की व्याख्या नहीं कर सका। मैंने यह मान लिया था कि Invoke की लागत + = की तुलना में बहुत अधिक है, लेकिन यह अनुमान योग्य है कि वे हार्डवेयर ऑप्टिमाइज़ेशन के आधार पर बहुत करीब हो सकते हैं।
जॉन त्सेंग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.