अगर मैं ToLookup से पहले एक अतिरिक्त ToArray लगाऊं तो यह तेज़ क्यों है?


10

हमारे पास एक छोटी विधि है जो पार्स .csv फ़ाइल को लुकअप करने के लिए है:

ILookup<string, DgvItems> ParseCsv( string fileName )
{
    var file = File.ReadAllLines( fileName );
    return file.Skip( 1 ).Select( line => new DgvItems( line ) ).ToLookup( item => item.StocksID );
}

और DgvItems की परिभाषा:

public class DgvItems
{
    public string DealDate { get; }

    public string StocksID { get; }

    public string StockName { get; }

    public string SecBrokerID { get; }

    public string SecBrokerName { get; }

    public double Price { get; }

    public int BuyQty { get; }

    public int CellQty { get; }

    public DgvItems( string line )
    {
        var split = line.Split( ',' );
        DealDate = split[0];
        StocksID = split[1];
        StockName = split[2];
        SecBrokerID = split[3];
        SecBrokerName = split[4];
        Price = double.Parse( split[5] );
        BuyQty = int.Parse( split[6] );
        CellQty = int.Parse( split[7] );
    }
}

और हमने पाया कि यदि हम इस तरह ToArray()से पहले एक अतिरिक्त जोड़ते हैं ToLookup():

static ILookup<string, DgvItems> ParseCsv( string fileName )
{
    var file = File.ReadAllLines( fileName  );
    return file.Skip( 1 ).Select( line => new DgvItems( line ) ).ToArray().ToLookup( item => item.StocksID );
}

उत्तरार्द्ध काफी तेज है। विशेष रूप से, 1.4 मिलियन लाइनों के साथ परीक्षण फ़ाइल का उपयोग करते समय, पूर्व में लगभग 4.3 सेकंड लगते हैं और बाद में लगभग 3 सेकंड लगते हैं।

मुझे उम्मीद है ToArray()कि अतिरिक्त समय लेना चाहिए ताकि बाद थोड़ा धीमा हो। यह वास्तव में तेज क्यों है?


अतिरिक्त जानकारी:

  1. हमें यह समस्या मिली क्योंकि एक और तरीका है जो समान .csv फ़ाइल को अलग-अलग प्रारूप में रखता है और इसमें लगभग 3 सेकंड लगते हैं इसलिए हमें लगता है कि यह एक ही चीज़ को 3 सेकंड में करने में सक्षम होना चाहिए।

  2. मूल डेटा प्रकार है Dictionary<string, List<DgvItems>>और मूल कोड linq का उपयोग नहीं किया है और परिणाम समान है।


बेंचमार्कडॉटनेट टेस्ट क्लास:

public class TestClass
{
    private readonly string[] Lines;

    public TestClass()
    {
        Lines = File.ReadAllLines( @"D:\20110315_Random.csv" );
    }

    [Benchmark]
    public ILookup<string, DgvItems> First()
    {
        return Lines.Skip( 1 ).Select( line => new DgvItems( line ) ).ToArray().ToLookup( item => item.StocksID );
    }

    [Benchmark]
    public ILookup<string, DgvItems> Second()
    {
        return Lines.Skip( 1 ).Select( line => new DgvItems( line ) ).ToLookup( item => item.StocksID );
    }
}

परिणाम:

| Method |    Mean |    Error |   StdDev |
|------- |--------:|---------:|---------:|
|  First | 2.530 s | 0.0190 s | 0.0178 s |
| Second | 3.620 s | 0.0217 s | 0.0203 s |

मैंने मूल कोड के आधार पर एक और परीक्षण किया। लगता है कि समस्या Linq पर नहीं है।

public class TestClass
{
    private readonly string[] Lines;

    public TestClass()
    {
        Lines = File.ReadAllLines( @"D:\20110315_Random.csv" );
    }

    [Benchmark]
    public Dictionary<string, List<DgvItems>> First()
    {
        List<DgvItems> itemList = new List<DgvItems>();
        for ( int i = 1; i < Lines.Length; i++ )
        {
            itemList.Add( new DgvItems( Lines[i] ) );
        }

        Dictionary<string, List<DgvItems>> dictionary = new Dictionary<string, List<DgvItems>>();

        foreach( var item in itemList )
        {
            if( dictionary.TryGetValue( item.StocksID, out var list ) )
            {
                list.Add( item );
            }
            else
            {
                dictionary.Add( item.StocksID, new List<DgvItems>() { item } );
            }
        }

        return dictionary;
    }

    [Benchmark]
    public Dictionary<string, List<DgvItems>> Second()
    {
        Dictionary<string, List<DgvItems>> dictionary = new Dictionary<string, List<DgvItems>>();
        for ( int i = 1; i < Lines.Length; i++ )
        {
            var item = new DgvItems( Lines[i] );

            if ( dictionary.TryGetValue( item.StocksID, out var list ) )
            {
                list.Add( item );
            }
            else
            {
                dictionary.Add( item.StocksID, new List<DgvItems>() { item } );
            }
        }

        return dictionary;
    }
}

परिणाम:

| Method |    Mean |    Error |   StdDev |
|------- |--------:|---------:|---------:|
|  First | 2.470 s | 0.0218 s | 0.0182 s |
| Second | 3.481 s | 0.0260 s | 0.0231 s |

2
मुझे परीक्षण कोड / मापने पर अत्यधिक संदेह है। कृपया उस कोड को पोस्ट करें जो समय की गणना करता है
Erno

1
मेरा अनुमान है कि .ToArray()कॉल के बिना , कॉल करने .Select( line => new DgvItems( line ) )से पहले एक IEnumerable रिटर्न करने के लिए ToLookup( item => item.StocksID )। और एक विशेष तत्व को देखना ऐरे की तुलना में IEnumerable का उपयोग करना बदतर है। संभवतः एक सरणी में बदलने के लिए और ienumerable का उपयोग करके लुकअप करने के लिए तेज़ी से।
किंबौड़ी

2
साइड नोट: डाल var file = File.ReadLines( fileName );- के ReadLinesबजाय ReadAllLinesऔर आप कोड शायद तेज हो जाएगा
दिमित्री Bychenko

2
आपको BenchmarkDotnetवास्तविक पूर्ण माप के लिए उपयोग करना चाहिए । इसके अलावा, उस वास्तविक कोड को आज़माएं और अलग करें जिसे आप मापना चाहते हैं और परीक्षण में IO को शामिल नहीं करते हैं।
जोहान

1
मुझे नहीं पता कि यह क्यों घट गया - मुझे लगता है कि यह एक अच्छा सवाल है।
रुफस एल

जवाबों:


2

मैं नीचे दिए गए सरलीकृत कोड के साथ समस्या को दोहराने में कामयाब रहा:

var lookup = Enumerable.Range(0, 2_000_000)
    .Select(i => ( (i % 1000).ToString(), i.ToString() ))
    .ToArray() // +20% speed boost
    .ToLookup(x => x.Item1);

यह महत्वपूर्ण है कि निर्मित ट्यूपल के सदस्य तार हैं। .ToString()उपरोक्त कोड से दो को हटाने से फायदा खत्म हो जाता है ToArray। .NET फ्रेमवर्क .NET कोर की तुलना में थोड़ा अलग व्यवहार करता है, क्योंकि यह केवल .ToString()मनाया अंतर को खत्म करने के लिए पहले को हटाने के लिए पर्याप्त है ।

मुझे नहीं पता कि ऐसा क्यों होता है।


आपने किस रूपरेखा के साथ इसकी पुष्टि की? मैं .net फ्रेमवर्क 4.7.2
मैग्नस

@ मैग्नस .NET फ्रेमवर्क 4.8 (वीएस 2019, रिलीज़ बिल्ड)
थियोडोर ज़ूलियास

प्रारंभ में मैंने देखा अंतर को अतिरंजित किया। यह .NET कोर में लगभग 20% और .NET फ्रेमवर्क में लगभग 10% है।
थियोडोर जूलियास

1
अच्छा रेप्रो। मुझे इसकी कोई ख़ास जानकारी नहीं है कि ऐसा क्यों होता है और यह पता लगाने का समय नहीं है, लेकिन मेरे अनुमान यह होगा कि ToArrayया ToListडेटा सन्निहित स्मृति में रहने के लिए बाध्य करता है; पाइपलाइन में एक विशेष चरण में मजबूर करने के बावजूद, यह लागत को जोड़ता है, बाद में ऑपरेशन के कारण कम प्रोसेसर कैश मिस हो सकता है; प्रोसेसर कैश मिस आश्चर्यजनक रूप से महंगे हैं।
एरिक लिपर्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.