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

후자는 상당히 빠릅니다. 보다 구체적으로, 140 만 줄의 테스트 파일을 사용할 경우 전자는 약 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을 사용하면 특정 요소를 찾는 것이 더 나쁩니다. 아마도 ienumerable을 사용하는 것보다 배열로 변환하고 조회를 수행하는 것이 더 빠릅니다.
kimbaudi

2
사이드 참고 : 넣어 var file = File.ReadLines( fileName );- ReadLines대신 ReadAllLines당신은 코드 아마 빨라집니다
드미트리 Bychenko

2
BenchmarkDotnet실제 성능 측정에 사용해야 합니다. 또한 측정하려는 실제 코드를 격리하고 테스트에 IO를 포함시키지 마십시오.
JohanP

1
왜 이것이 공감대를 얻었는지 모르겠습니다. 좋은 질문이라고 생각합니다.
Rufus 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);

작성된 튜플의 멤버는 문자열이어야합니다. .ToString()위 코드에서 두 가지 를 제거하면의 이점이 사라 ToArray집니다. .NET Framework .ToString()는 관찰 된 차이를 제거하기 위해 첫 번째 만 제거하면 충분하므로 .NET Core와 약간 다르게 동작합니다 .

왜 이런 일이 일어나는지 모르겠습니다.


어떤 프레임 워크로이를 확인 했습니까? .net 프레임 워크 4.7.2
Magnus

@ Magnus .NET Framework 4.8 (VS 2019, 릴리스 빌드)
Theodor Zoulias

처음에 나는 관찰 된 차이를 과장했다. .NET Core에서는 약 20 %, .NET Framework에서는 약 10 %입니다.
Theodor Zoulias

1
좋은 재현. 나는 이런 일이 발생하고 그것을 알아낼 시간이 없어,하지만 내 왜 특정 지식이없는 추측 있다는 것 ToArray또는 ToList힘 데이터가 연속 메모리에있을를; 파이프 라인의 특정 단계에서 강제 실행하면 비용이 추가 되더라도 이후 작업에서 프로세서 캐시 누락이 줄어들 수 있습니다. 프로세서 캐시 미스는 놀랍도록 비싸다.
Eric Lippert
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.