.net - सकत - मोबाइल कॉल रिकॉर्डिंग अदालतों में स्वीकार्य हैं




LINQ प्रश्नों में ToList() या ToArray() को कॉल करना बेहतर है? (10)

(सात साल बाद ...)

कुछ अन्य (अच्छे) उत्तरों ने माइक्रोस्कोपिक प्रदर्शन मतभेदों पर ध्यान केंद्रित किया है जो घटित होंगे।

यह पोस्ट एक List<T> द्वारा लौटाए गए की तुलना में एक सरणी ( T[] ) द्वारा उत्पादित IEnumerator<T> बीच मौजूद अर्थपूर्ण अंतर का उल्लेख करने के लिए सिर्फ एक पूरक है।

उदाहरण के साथ सबसे अच्छा सचित्र:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

उपरोक्त कोड कोई अपवाद नहीं चलाएगा और आउटपुट उत्पन्न करेगा:

1
2
3
4
5
6
7
8
900
10

इससे पता चलता है कि IEnumarator<int> एक int[] द्वारा लौटाया गया है, इस पर ट्रैक नहीं रखता है कि एन्यूमरेटर के निर्माण के बाद सरणी को संशोधित किया गया है या नहीं।

ध्यान दें कि मैंने स्थानीय चर source को IList<int> रूप में घोषित किया है। इस तरह से मैं सुनिश्चित करता हूं कि सी # कंपाइलर फ़ोरैच स्टेटमेंट को उस चीज़ में ऑप्टिमाइज़ नहीं करता है जो कि for (var idx = 0; idx < source.Length; idx++) { /* ... */ } लूप के बराबर है। अगर मैं var source = ...; उपयोग करता हूं तो यह कुछ # सी # कंपाइलर कर सकता है var source = ...; बजाय। .NET ढांचे के मेरे वर्तमान संस्करण में यहां उपयोग किए गए वास्तविक गणक एक गैर-सार्वजनिक संदर्भ-प्रकार System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32] लेकिन निश्चित रूप से यह एक कार्यान्वयन विवरण है।

अब, अगर मैं बदलता .ToArray() में। .ToList() , मुझे केवल मिलता है:

1
2
3
4
5

उसके बाद एक System.InvalidOperationException झटका-अप कह रहा है:

संग्रह संशोधित किया गया था; गणना ऑपरेशन निष्पादित नहीं हो सकता है।

इस मामले में अंतर्निहित गणक सार्वजनिक म्यूटेबल वैल्यू-टाइप System.Collections.Generic.List`1+Enumerator[System.Int32] (इस मामले में एक IEnumerator<int> बॉक्स के अंदर बॉक्स किया गया क्योंकि मैं IList<int> उपयोग करता हूं)।

अंत में, List<T> द्वारा उत्पादित गणनाकर्ता इस बात पर ध्यान रखता है कि सूची गणना के दौरान बदलती है, जबकि T[] द्वारा उत्पादित गणनाकर्ता नहीं है। तो .ToList() और .ToArray() बीच चयन करते समय इस अंतर पर विचार करें।

लोग अक्सर एक अतिरिक्त .ToArray() या .ToList() एक संग्रह को बाधित करने के लिए जो ट्रैक करता है कि यह एक गणक के जीवनकाल के दौरान संशोधित किया गया था या नहीं।

(अगर कोई यह जानना चाहता है कि List<> संग्रह को संशोधित करता है या नहीं, इस वर्ग में एक निजी फ़ील्ड _version है जो List<> अपडेट हो जाता है।

मैं अक्सर उस मामले में भाग लेता हूं जहां मैं एक प्रश्न को ठीक करना चाहता हूं जहां मैं इसे घोषित करता हूं। यह आमतौर पर इसलिए होता है क्योंकि मुझे इसे कई बार फिर से शुरू करने की आवश्यकता होती है और गणना करना महंगा होता है। उदाहरण के लिए:

string raw = "...";
var lines = (from l in raw.Split('\n')
             let ll = l.Trim()
             where !string.IsNullOrEmpty(ll)
             select ll).ToList();

यह ठीक काम करता है। लेकिन अगर मैं परिणाम संशोधित नहीं कर रहा हूं, तो मैं ToArray() बजाय ToArray() को भी कॉल कर सकता हूं।

मुझे आश्चर्य है कि क्या ToArray () को पहली बार ToList () द्वारा कॉल किया गया है और इसलिए ToList () को कॉल करने से कम स्मृति कुशल है।

मैं पागल हो रहा हूँ? क्या मुझे सिर्फ ToArray() - ज्ञान में सुरक्षित और सुरक्षित होना चाहिए कि स्मृति दो बार आवंटित नहीं की जाएगी?


आदर्श विकल्प ToArray आधार पर आपको ToList या ToArray लिए जाने का निर्णय लेना चाहिए। यदि आप एक ऐसा संग्रह चाहते हैं जिसे केवल इंडेक्स द्वारा पुनरावृत्त किया जा सके और एक्सेस किया जा सके, तो ToArray चुनें। यदि आप बिना किसी परेशानी के बाद संग्रह से जोड़ने और निकालने की अतिरिक्त क्षमताओं को चाहते हैं, तो एक ToList (वास्तव में नहीं कि आप किसी सरणी में जोड़ नहीं सकते हैं, लेकिन यह आमतौर पर इसके लिए सही उपकरण नहीं है)।

यदि प्रदर्शन महत्वपूर्ण है, तो आपको यह भी विचार करना चाहिए कि किस पर काम करना तेज़ होगा। वास्तव में, आप दस लाख बार ToList या ToArray कॉल नहीं ToList , लेकिन प्राप्त संग्रह पर दस लाख बार काम कर सकते हैं। उस सम्मान में [] बेहतर है, क्योंकि कुछ ओवरहेड के साथ List<> है [] । कुछ दक्षता तुलना के लिए यह धागा देखें: कौन सा अधिक कुशल है: सूची <int> या int []

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


जब तक आपको अन्य बाधाओं को पूरा करने के लिए केवल एक सरणी की आवश्यकता नहीं है, तो आपको ToList उपयोग करना चाहिए। अधिकांश परिदृश्यों में ToArray की तुलना में अधिक स्मृति आवंटित करेगा।

दोनों भंडारण के लिए सरणी का उपयोग करते हैं, लेकिन ToList में अधिक लचीला बाधा है। संग्रह में तत्वों की संख्या जितनी बड़ी हो उतनी बड़ी सरणी की आवश्यकता होती है। यदि सरणी बड़ी है, तो यह कोई समस्या नहीं है। हालांकि ToArray को तत्वों की संख्या के बराबर आकार देने के लिए सरणी की आवश्यकता होती है।

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

संपादित करें

कुछ लोगों ने मुझे List<T> मूल्य में अतिरिक्त अप्रयुक्त स्मृति होने के परिणाम के बारे में पूछा है।

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

आम तौर पर हालांकि मुझे यह दुर्लभ मामला लगता है। बहुत सारे ToArray कॉल को देखना आम बात है जो तुरंत स्मृति के अन्य अल्पकालिक उपयोगों को पारित कर दिया जाता है, जिसमें मामला ToList बेहतर रूप से बेहतर होता है।

यहां कुंजी प्रोफाइल, प्रोफाइल और फिर प्रोफाइल करने के लिए है।


प्रदर्शन अंतर महत्वहीन होगा, क्योंकि List<T> को गतिशील आकार के सरणी के रूप में कार्यान्वित किया गया है। ToArray() कॉल करना (जो सरणी को बढ़ाने के लिए एक आंतरिक Buffer<T> कक्षा का उपयोग करता है) या ToList() (जो List<T>(IEnumerable<T>) कन्स्ट्रक्टर ToList() उन्हें इन्हें डालने का विषय बन जाएगा एक सरणी और सरणी बढ़ रही है जब तक कि यह उन सभी फिट बैठता है।

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


मैं @mquander से सहमत हूं कि प्रदर्शन अंतर महत्वहीन होना चाहिए। हालांकि, मैं इसे सुनिश्चित करने के लिए बेंचमार्क करना चाहता था, इसलिए मैंने किया - और यह महत्वहीन है।

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

प्रत्येक स्रोत सरणी / सूची में 1000 तत्व थे। तो आप देख सकते हैं कि दोनों समय और स्मृति अंतर नगण्य हैं।

मेरा निष्कर्ष: आप ToList () का भी उपयोग कर सकते हैं, क्योंकि एक List<T> एक सरणी से अधिक कार्यक्षमता प्रदान करती है, जब तक कि स्मृति के कुछ बाइट वास्तव में आपके लिए महत्वपूर्ण न हों।


मैंने पाया कि अन्य बेंचमार्क लोगों ने यहां कमी की है, इसलिए यहां मेरी दरार है। अगर आपको मेरी पद्धति के साथ कुछ गलत लगता है तो मुझे बताएं।

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

आप यहां LINQPad स्क्रिप्ट डाउनलोड कर सकते हैं

परिणाम:

ऊपर दिए गए कोड को ट्वीव करना, आपको पता चलेगा कि:

  1. छोटे सरणी से निपटने के दौरान अंतर कम महत्वपूर्ण है।
  2. string एस के बजाय int से निपटने पर अंतर कम महत्वपूर्ण होता है।
  3. string एस के बजाय बड़ी struct का उपयोग करना आम तौर पर बहुत अधिक समय लेता है, लेकिन वास्तव में अनुपात को बहुत अधिक नहीं बदलता है।

यह शीर्ष वोट वाले उत्तरों के निष्कर्षों से सहमत है:

  1. जब तक आपका कोड अक्सर डेटा की कई बड़ी सूचियां नहीं बना रहा हो, तब तक आपको प्रदर्शन अंतर दिखाई देने की संभावना नहीं है। (100K स्ट्रिंग्स की 1000 सूचियों को बनाते समय केवल 200ms अंतर था।)
  2. ToList() लगातार तेज़ी से चलता है, और यदि आप लंबे समय तक परिणामों पर लटकने की योजना नहीं बना रहे हैं तो बेहतर विकल्प होगा।

अद्यतन करें

@ जोनहन्ना ने इंगित किया कि Select के क्रियान्वयन के आधार पर परिणामस्वरूप संग्रह के आकार की भविष्यवाणी करने के लिए ToList() या ToArray() कार्यान्वयन के लिए यह संभव है। प्रतिस्थापित करें .Select(i => i) उपरोक्त कोड में Where(i => true) इस समय बहुत समान परिणाम उत्पन्न करता है , और .NET कार्यान्वयन के बावजूद ऐसा करने की अधिक संभावना है।


स्मृति हमेशा दो बार आवंटित की जाएगी - या उसके करीब कुछ। चूंकि आप किसी सरणी का आकार बदल नहीं सकते हैं, दोनों विधियां बढ़ते संग्रह में डेटा एकत्र करने के लिए किसी प्रकार की तंत्र का उपयोग करेंगी। (खैर, सूची खुद में एक बढ़ता संग्रह है।)

सूची एक सरणी को आंतरिक भंडारण के रूप में उपयोग करती है, और आवश्यकता होने पर क्षमता को दोगुना करती है। इसका मतलब यह है कि वस्तुओं के औसत 2/3 कम से कम एक बार फिर से आवंटित किए जाते हैं, उनमें से आधे कम से कम दो बार आवंटित होते हैं, उनमें से आधा कम से कम तीन बार, और इसी तरह। इसका मतलब है कि प्रत्येक आइटम को औसतन 1.3 बार फिर से आवंटित किया गया है, जो बहुत अधिक ओवरहेड नहीं है।

यह भी याद रखें कि यदि आप तारों को जोड़ रहे हैं, तो संग्रह में केवल तारों के संदर्भ शामिल होते हैं, तारों को स्वयं पुन: आवंटित नहीं किया जाता है।


ToList() को आमतौर पर प्राथमिकता दी जाती है यदि आप इसे ToList() IEnumerable<T> (उदाहरण के लिए ORM से) पर उपयोग करते हैं। यदि अनुक्रम की लंबाई शुरुआत में ज्ञात नहीं है, तो ToArray() गतिशील-लंबाई संग्रह को सूची की तरह बनाता है और फिर इसे सरणी में परिवर्तित करता है, जिसमें अतिरिक्त समय लगता है।


संपादित करें : इस उत्तर का अंतिम भाग मान्य नहीं है। हालांकि, बाकी अभी भी उपयोगी जानकारी है, इसलिए मैं इसे छोड़ दूंगा।

मुझे पता है कि यह एक पुरानी पोस्ट है, लेकिन एक ही प्रश्न रखने और कुछ शोध करने के बाद, मुझे कुछ दिलचस्प पाया गया है जो साझा करने के लायक हो सकता है।

सबसे पहले, मैं @mquander और उसके उत्तर से सहमत हूं। वह कहने में सही है कि प्रदर्शन के अनुसार, दोनों समान हैं।

हालांकि, मैं System.Linq.Enumerable में विधियों को देखने के लिए परावर्तक का उपयोग कर रहा हूं। System.Linq.Enumerable एक्सटेंशन नेमस्पेस, और मैंने एक बहुत ही सामान्य अनुकूलन देखा है।
जब भी संभव हो, विधि को अनुकूलित करने के लिए IEnumerable<T> स्रोत को IList<T> या ICollection<T> पर डाला जाता है। उदाहरण के लिए, ElementAt(int)

दिलचस्प बात यह है कि माइक्रोसॉफ्ट ने केवल IList<T> लिए अनुकूलन करना चुना, लेकिन IList नहीं। ऐसा लगता है कि माइक्रोसॉफ्ट IList<T> इंटरफ़ेस का उपयोग करना पसंद करता है।

System.Array केवल IList लागू करता है, इसलिए इन एक्सटेंशन अनुकूलन में से किसी से भी इसका लाभ नहीं होगा।
इसलिए, मैं प्रस्तुत करता हूं कि सबसे अच्छा अभ्यास .ToList() विधि का उपयोग करना है।
यदि आप किसी भी एक्सटेंशन विधियों का उपयोग करते हैं, या सूची को किसी अन्य विधि में पास करते हैं, तो एक मौका है कि इसे IList<T> लिए अनुकूलित किया जा सकता है।


For anyone interested in using this result in another Linq-to-sql such as

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

then the SQL that is generated is the same whether you used a List or Array for the myListOrArray. Now I know some may ask why even enumerate before this statement, but there is a difference between the SQL generated from an IQueryable vs (List or Array).





performance