ToLookup'tan önce fazladan bir ToArray koyarsam neden daha hızlı?


10

.Csv dosyasını aramaya ayıran kısa bir yöntemimiz var:

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

Ve DgvItems'in tanımı:

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] );
    }
}

Ve daha ToArray()önce ToLookup()böyle bir ekstra eklersek :

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 );
}

İkincisi önemli ölçüde daha hızlıdır. Daha spesifik olarak, 1,4 milyon satırlı test dosyası kullanıldığında, ilk dosya yaklaşık 4,3 saniye, ikincisi yaklaşık 3 saniye sürer.

ToArray()Ekstra zaman almasını bekliyorum , böylece ikincisi biraz daha yavaş olmalı. Neden daha hızlı?


Ek bilgi:

  1. Bu sorunu, aynı .csv dosyasını farklı biçime ayrıştıran başka bir yöntem olduğundan ve yaklaşık 3 saniye sürdüğünden, bunun aynı şeyi 3 saniyede yapabilmesi gerektiğini düşündüğümüz için bulduk.

  2. Orijinal veri türü Dictionary<string, List<DgvItems>>ve orijinal kod linq kullanmadı ve sonuç benzer.


BenchmarkDotNet test sınıfı:

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 );
    }
}

Sonuç:

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

Orijinal kod üzerinde başka bir test üssü yaptım. Sorunun Linq'te olmadığı anlaşılıyor.

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;
    }
}

Sonuç:

| 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
Test kodundan / ölçümünden çok şüpheleniyorum. Lütfen zamanı hesaplayan kodu gönderin
Erno

1
Benim tahminim olmadan, .ToArray()çağrı çağrılmadan .Select( line => new DgvItems( line ) )önce bir IEnumerable döndürür ToLookup( item => item.StocksID ). Belirli bir öğeye bakmak, Array'dan IEnumerable kullanmaktan daha kötüdür. Bir diziye dönüştürmek ve arama yapmak mümkün olandan daha hızlıdır.
kimbaudi

2
Yan Not: koymak var file = File.ReadLines( fileName );- ReadLinesyerine ReadAllLinesve kod muhtemelen daha hızlı olacaktır
Dmitry Bychenko

2
BenchmarkDotnetGerçek perf ölçümü için kullanmalısınız . Ayrıca, teste IO eklememek için ölçmek istediğiniz gerçek kodu deneyin ve izole edin.
JohanP

1
Bunun neden bir düşüş yaptığını bilmiyorum - bence bu iyi bir soru.
Rufus L

Yanıtlar:


2

Aşağıdaki basit kod ile sorunu çoğaltmayı başardım:

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

Oluşturulan demet üyelerinin dize olması önemlidir. .ToString()Yukarıdaki koddan ikisinin kaldırılması avantajını ortadan kaldırır ToArray. .NET Framework, .NET Core'dan biraz farklı davranır, çünkü yalnızca .ToString()gözlemlenen farkı ortadan kaldırmak için ilkini kaldırmak yeterlidir .

Bunun neden olduğu hakkında hiçbir fikrim yok.


Bunu hangi çerçeveyle onayladınız? .Net framework 4.7.2
Magnus

@Magnus .NET Framework 4.8 (VS 2019, Sürüm Oluşturma)
Theodor Zoulias

Başlangıçta gözlemlenen farkı abarttım. .NET Core'da yaklaşık% 20 ve .NET Framework'te yaklaşık% 10'dur.
Theodor Zoulias

1
Güzel repro. Bunun neden oluştuğuna dair özel bir bilgim yok ve bunu çözmek için zamanım yok, ama tahminim , ToArrayya da ToListverilerin bitişik bellekte olmasını zorlayacak; boru hattında belirli bir aşamada bu zorlamanın yapılması, maliyet katmasına rağmen, daha sonraki bir işlemin daha az işlemci önbellek kaybına neden olabilir; işlemci önbellek özlüyor şaşırtıcı derecede pahalı.
Eric Lippert
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.