2012-04-25 23 views
7

मान लीजिए मैं निम्नलिखित कोड है:क्या LINQ कैश गणना मान करता है?

var X = XElement.Parse (@" 
    <ROOT> 
     <MUL v='2' /> 
     <MUL v='3' /> 
    </ROOT> 
"); 
Enumerable.Range (1, 100) 
    .Select (s => X.Elements() 
     .Select (t => Int32.Parse (t.Attribute ("v").Value)) 
     .Aggregate (s, (t, u) => t * u) 
    ) 
    .ToList() 
    .ForEach (s => Console.WriteLine (s)); 

क्या .NET रनटाइम वास्तव में यहाँ कर रहा है? क्या यह गुणों को पार्सिंग और कनवर्ट करने के लिए 100 बार में से प्रत्येक को पूर्णांक में परिवर्तित कर रहा है, या यह समझने के लिए पर्याप्त स्मार्ट है कि इसे पार्स किए गए मानों को कैश करना चाहिए और सीमा में प्रत्येक तत्व के लिए गणना दोहराएं?

इसके अलावा, मैं इस तरह कुछ समझने के बारे में कैसे जाऊं?

आपकी मदद के लिए अग्रिम धन्यवाद।

+2

"मैं इस तरह कुछ ऐसा करने के बारे में कैसे जाउंगा" - सबसे अच्छा शॉट इस कोड से उत्पन्न आईएल का अध्ययन करना है। – Andrey

+1

आप पार्स() विधि पर एक डीबगर ब्रेकपॉइंट सेट कर सकते हैं और देख सकते हैं कि यह कितनी बार हिट करता है। प्रतिक्रिया के लिए –

उत्तर

2

यह कुछ समय हो गया है क्योंकि मैंने इस कोड के माध्यम से खोला है, लेकिन आईआईआरसी, जिस तरह से Select काम करता है, उसे Func को कैश करना है और इसे एक समय में स्रोत संग्रह पर चलाएं। इसलिए, बाहरी सीमा में प्रत्येक तत्व के लिए, यह आंतरिक Select/Aggregate अनुक्रम चलाएगा जैसे कि यह पहली बार था। कोई भी अंतर्निर्मित कैशिंग नहीं चल रहा है - आपको स्वयं अभिव्यक्तियों में इसे लागू करना होगा।

आप अपने आप को यह पता लगा करना चाहता था, तो आप तीन बुनियादी विकल्प मिल गया है:

  1. कोड संकलित करें और आईएल देखने पर ildasm का उपयोग करें; यह सबसे सटीक है, लेकिन विशेष रूप से लैम्ब्डा और बंद होने के साथ, आपको आईएल से जो मिलता है वह सी # कंपाइलर में जो कुछ भी डालता है, उससे कुछ भी नहीं दिख सकता है।
  2. सिस्टम को हटाने के लिए dotPeek जैसे कुछ का उपयोग करें। Linq.dll C# में; दोबारा, आप इस तरह के औजारों से बाहर निकलते हैं, केवल मूल स्रोत कोड के समान ही हो सकते हैं, लेकिन कम से कम यह सी # होगा (और विशेष रूप से डॉटपीक विशेष रूप से एक अच्छी अच्छी नौकरी करता है, और मुफ़्त है।)
  3. मेरी व्यक्तिगत वरीयता - .NET 4.0 Reference Source डाउनलोड करें और स्वयं को ढूंढें; यह वही है :) आपको बस एमएस पर भरोसा करना है कि संदर्भ स्रोत द्विआधारी उत्पादन के लिए उपयोग किए जाने वाले वास्तविक स्रोत से मेल खाता है, लेकिन मुझे शक करने का कोई अच्छा कारण नहीं दिख रहा है।
  4. जैसा कि @AllonGuralnek द्वारा इंगित किया गया है, आप एक ही पंक्ति के भीतर विशिष्ट लैम्ब्डा अभिव्यक्तियों पर ब्रेकपॉइंट सेट कर सकते हैं; अपने कर्सर को लैम्ब्डा के शरीर के अंदर कहीं भी रखें और एफ 9 दबाएं और यह केवल लैम्ब्डा को तोड़ देगा। (क्या आप गलत यह करना है, यह ब्रेकप्वाइंट रंग में पूरी लाइन पर प्रकाश डाला, अगर आप इसे सही करते हैं, यह सिर्फ लैम्ब्डा पर प्रकाश डाला जाएगा।)
+0

धन्यवाद। मैं पहली और तीसरी विधियों का प्रयास करूंगा। – Shredderroy

+2

4. अपने कर्सर को '=>' के बाद रखें और F9 दबाएं। वह लैम्ब्डा के अंदर एक ब्रेकपॉइंट लगाएगा और जब यह पहुंच जाएगा तो टूट जाएगा। प्रत्येक लैम्ब्डा के लिए दोहराएं और आपको एक अच्छा निशान मिल जाएगा जिसे कब कहा जाता है। –

+0

@AllonGuralnek यह एक अच्छा बिंदु है, मैं lambdas को तोड़ने के बारे में भूल जाता हूं क्योंकि मैं आमतौर पर उन्हें सेट करने के लिए माउस का उपयोग करता हूं :) –

4

LINQ और IEnumerable<T>पुल आधारित है। इसका अर्थ यह है कि सामान्य रूप से LINQ कथन का हिस्सा होने वाले भविष्यवाणियों और कार्रवाइयों को तब तक निष्पादित नहीं किया जाता है जब तक मूल्यों को खींचा नहीं जाता है। इसके अलावा भविष्यवाणी और क्रियाएं हर बार मूल्यों को खींचा जाएगा (उदाहरण के लिए कोई गुप्त कैशिंग चल रहा है)।

एक IEnumerable<T> से खींच foreach बयान है जो वास्तव में IEnumerable<T>.GetEnumerator() बुला और बार-बार मूल्यों को खींचने के लिए IEnumerator<T>.MoveNext() को फोन करके एक प्रगणक प्राप्त करने के लिए वाक्यात्मक चीनी है द्वारा किया जाता है।

ToList(), ToArray(), ToDictionary() और ToLookup() तरह

LINQ ऑपरेटरों एक foreach बयान लपेटता तो इन तरीकों एक पुल करेंगे। Aggregate(), Count() और First() जैसे ऑपरेटरों के बारे में भी यही कहा जा सकता है। इन विधियों में आम बात है कि वे एक एकल परिणाम उत्पन्न करते हैं जिसे foreach कथन निष्पादित करके बनाया जाना है।

कई LINQ ऑपरेटर एक नया IEnumerable<T> अनुक्रम उत्पन्न करते हैं। जब परिणामी अनुक्रम से एक तत्व खींचा जाता है तो ऑपरेटर स्रोत अनुक्रम से एक या अधिक तत्व खींचता है। Select() ऑपरेटर सबसे स्पष्ट उदाहरण है लेकिन अन्य उदाहरण SelectMany(), Where(), Concat(), Union(), Distinct(), Skip() और Take() हैं। ये ऑपरेटर कोई कैशिंग नहीं करते हैं। जब 0'से N'th तत्व खींचा जाता है तो यह स्रोत अनुक्रम से N'th तत्व खींचता है, आपूर्ति की गई क्रिया का उपयोग करके प्रक्षेपण लागू करता है और इसे वापस करता है। यहाँ कुछ भी गुप्त नहीं जा रहा है।

अन्य LINQ ऑपरेटरों ने भी IEnumerable<T> अनुक्रमों का उत्पादन किया है, लेकिन वे वास्तव में पूरे स्रोत अनुक्रम को खींचकर, अपना काम कर रहे हैं और फिर एक नया अनुक्रम तैयार करके कार्यान्वित किए जाते हैं। इन विधियों में Reverse(), OrderBy() और GroupBy() शामिल हैं। हालांकि, ऑपरेटर द्वारा किए गए पुल को केवल तभी किया जाता है जब ऑपरेटर को खींच लिया जाता है जिसका मतलब है कि आपको अभी भी कुछ भी निष्पादित होने से पहले LINQ कथन के "अंत में" foreach लूप की आवश्यकता है। आप तर्क दे सकते हैं कि ये ऑपरेटर कैश का उपयोग करते हैं क्योंकि वे तुरंत संपूर्ण स्रोत अनुक्रम खींचते हैं। हालांकि, यह कैश प्रत्येक बार ऑपरेटर को फिर से चालू किया जाता है, इसलिए यह वास्तव में एक कार्यान्वयन विवरण है और कुछ ऐसा नहीं है जो जादुई रूप से पता लगाएगा कि आप उसी अनुक्रम में एक ही OrderBy() ऑपरेशन को कई बार लागू कर रहे हैं।


आपके उदाहरण में ToList() एक पुल करेगा। बाहरी Select में कार्रवाई 100 बार निष्पादित होगी। प्रत्येक बार जब यह क्रिया Aggregate() निष्पादित की जाती है तो एक और पुल करेगा जो एक्सएमएल विशेषताओं को पार्स करेगा। कुल मिलाकर आपका कोड Int32.Parse() 200 बार कॉल करेगा।

आप विशेषताओं खींच कर एक बार के बजाय प्रत्येक यात्रा पर इस सुधार कर सकते हैं:

var X = XElement.Parse (@" 
    <ROOT> 
     <MUL v='2' /> 
     <MUL v='3' /> 
    </ROOT> 
") 
.Elements() 
.Select (t => Int32.Parse (t.Attribute ("v").Value)) 
.ToList(); 
Enumerable.Range (1, 100) 
    .Select (s => x.Aggregate (s, (t, u) => t * u)) 
    .ToList() 
    .ForEach (s => Console.WriteLine (s)); 

अब Int32.Parse() केवल 2 बार कहा जाता है। हालांकि, लागत यह है कि विशेषता मूल्यों की एक सूची आवंटित, संग्रहित और अंततः कचरा एकत्रित किया जाना चाहिए। (सूची में दो तत्व होते हैं जब कोई बड़ी चिंता नहीं होती है।)

ध्यान दें कि यदि आप पहले ToList() भूल जाते हैं जो गुण खींचता है तो कोड अभी भी चल जाएगा लेकिन मूल कोड के समान सटीक प्रदर्शन विशेषताओं के साथ। गुणों को संग्रहीत करने के लिए कोई स्थान उपयोग नहीं किया जाता है लेकिन उन्हें प्रत्येक पुनरावृत्ति पर पार्स किया जाता है।

+0

विस्तृत प्रतिक्रिया के लिए बहुत बहुत धन्यवाद। – Shredderroy

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