ทำไมมันเร็วขึ้นถ้าฉันใส่ ToArray พิเศษก่อน ToLookup


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 และผลลัพธ์จะคล้ายกัน


ระดับการทดสอบ BenchmarkDotNet:

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 ได้ดีกว่า Array อาจเร็วกว่าที่จะแปลงเป็นอาร์เรย์และดำเนินการค้นหามากกว่าการใช้ ienumerable
kimbaudi

2
หมายเหตุด้านข้าง: ใส่var file = File.ReadLines( fileName );- ReadLinesแทนที่จะเป็นReadAllLinesและรหัสของคุณอาจจะเร็วกว่านี้
Dmitry Bychenko

2
คุณควรใช้BenchmarkDotnetสำหรับการตรวจวัดปริมาณความสมบูรณ์แบบจริง นอกจากนี้ลองแยกรหัสจริงที่คุณต้องการวัดและไม่รวม IO ในการทดสอบ
JohanP

1
ฉันไม่รู้ว่าทำไมสิ่งนี้ถึงลงคะแนนเสียงฉันคิดว่ามันเป็นคำถามที่ดี
รูฟัส L

คำตอบ:


2

ฉันจัดการเพื่อทำซ้ำปัญหาด้วยรหัสที่ง่ายด้านล่าง:

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

มันเป็นสิ่งสำคัญที่สมาชิกของ tuple ที่สร้างขึ้นเป็นสตริง การถอดสองจากโค้ดข้างต้นจะช่วยลดการใช้ประโยชน์จาก.ToString() ToArray.NET Framework ทำงานแตกต่างจาก. NET Core เล็กน้อยเนื่องจากเพียงพอที่จะลบเฉพาะส่วนแรก.ToString()เพื่อกำจัดความแตกต่างที่สังเกตได้

ฉันไม่รู้ว่าทำไมสิ่งนี้จึงเกิดขึ้น


คุณยืนยันด้วยกรอบไหน ฉันไม่เห็นความแตกต่างโดยใช้. net framework 4.7.2
Magnus

@Magnus .NET Framework 4.8 (VS 2019, Release Build)
Theodor Zoulias

ตอนแรกฉันพูดเกินจริงถึงความแตกต่างที่สังเกตได้ มันเป็นประมาณ 20% ใน. NET Core และประมาณ 10% ใน. NET Framework
Theodor Zoulias

1
นิสัยดี ฉันไม่มีความรู้เฉพาะเกี่ยวกับสาเหตุที่เกิดขึ้นและไม่มีเวลาที่จะคิดออก แต่ฉันเดาว่าจะเป็นToArrayหรือToListบังคับให้ข้อมูลอยู่ในความทรงจำที่ต่อเนื่องกัน; การทำเช่นนั้นบังคับให้อยู่ในขั้นตอนเฉพาะในไปป์ไลน์แม้ว่าจะเพิ่มต้นทุนอาจทำให้การดำเนินการในภายหลังมีแคชตัวประมวลผลที่น้อยลง หน่วยประมวลผลแคชที่พลาดไปนั้นมีราคาแพงอย่างน่าประหลาดใจ
Eric Lippert
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.