Tại sao nó nhanh hơn nếu tôi đặt thêm một ToArray trước ToLookup?


10

Chúng tôi có một phương pháp ngắn phân tích tệp .csv để tra cứu:

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

Và định nghĩa của 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] );
    }
}

Và chúng tôi thấy rằng nếu chúng tôi thêm một phần ToArray()trước ToLookup()như thế này:

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

Thứ hai là nhanh hơn đáng kể. Cụ thể hơn, khi sử dụng tệp thử nghiệm với 1,4 triệu dòng, cái trước mất khoảng 4,3 giây và cái sau mất khoảng 3 giây.

Tôi hy vọng ToArray()sẽ mất thêm thời gian để sau này nên chậm hơn một chút. Tại sao nó thực sự nhanh hơn?


Thông tin bổ sung:

  1. Chúng tôi đã tìm thấy sự cố này vì có một phương pháp khác phân tích cùng một tệp .csv sang định dạng khác và mất khoảng 3 giây nên chúng tôi nghĩ rằng phương pháp này sẽ có thể thực hiện điều tương tự trong 3 giây.

  2. Kiểu dữ liệu gốc là Dictionary<string, List<DgvItems>>và mã gốc không sử dụng linq và kết quả là tương tự nhau.


Lớp kiểm tra điểm chuẩnDotNet:

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

Kết quả:

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

Tôi đã làm một thử nghiệm khác dựa trên mã gốc. Có vẻ như vấn đề không nằm ở 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;
    }
}

Kết quả:

| 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
Tôi rất nghi ngờ mã kiểm tra / đo lường. Vui lòng gửi mã tính thời gian
Erno

1
Tôi đoán là không có .ToArray(), lệnh gọi .Select( line => new DgvItems( line ) )trả về IEnumerable trước khi gọi đến ToLookup( item => item.StocksID ). Và việc tìm kiếm một yếu tố cụ thể còn tệ hơn khi sử dụng IEnumerable so với Array. Có lẽ nhanh hơn để chuyển đổi thành một mảng và thực hiện tra cứu hơn là sử dụng một số lượng lớn.
kimbaudi

2
Lưu ý bên lề: đặt var file = File.ReadLines( fileName );- ReadLinesthay vì ReadAllLinesvà mã của bạn có thể sẽ nhanh hơn
Dmitry Bychenko

2
Bạn nên sử dụng BenchmarkDotnetđể đo hoàn hảo thực tế. Ngoài ra, hãy thử và cô lập mã thực tế bạn muốn đo và không bao gồm IO trong thử nghiệm.
JohanP

1
Tôi không biết lý do tại sao điều này có một downvote - tôi nghĩ đó là một câu hỏi hay.
Rufus L

Câu trả lời:


2

Tôi đã quản lý để sao chép vấn đề với mã đơn giản dưới đây:

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

Điều quan trọng là các thành viên của bộ dữ liệu được tạo là các chuỗi. Loại bỏ hai .ToString()từ mã trên loại bỏ lợi thế của ToArray. .NET Framework hoạt động hơi khác so với .NET Core, vì nó chỉ đủ loại bỏ cái đầu tiên .ToString()để loại bỏ sự khác biệt quan sát được.

Tôi không biết tại sao điều này xảy ra.


Khung nào bạn đã xác nhận điều này với? Tôi không thể thấy bất kỳ sự khác biệt nào khi sử dụng .net framework 4.7.2
Magnus

@Magnus .NET Framework 4.8 (VS 2019, Phát hành bản dựng)
Theodor Zoulias

Ban đầu tôi phóng đại sự khác biệt quan sát được. Đó là khoảng 20% ​​trong .NET Core và khoảng 10% trong .NET Framework.
Theodor Zoulias

1
Đẹp repro. Tôi không có kiến ​​thức cụ thể về lý do tại sao điều này xảy ra và không có thời gian để tìm ra nó, nhưng tôi đoánToArrayhoặc ToListbuộc dữ liệu phải ở trong bộ nhớ liền kề; thực hiện việc ép buộc đó ở một giai đoạn cụ thể trong đường ống, mặc dù nó làm tăng thêm chi phí, có thể khiến hoạt động sau này có ít lỗi bộ nhớ cache của bộ xử lý hơn; bộ nhớ cache bộ xử lý là đáng ngạc nhiên đắt tiền.
Eric Lippert
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.