Tenemos un método corto que analiza el archivo .csv a una búsqueda:
ILookup<string, DgvItems> ParseCsv( string fileName )
{
    var file = File.ReadAllLines( fileName );
    return file.Skip( 1 ).Select( line => new DgvItems( line ) ).ToLookup( item => item.StocksID );
}
Y la definición de 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] );
    }
}
Y descubrimos que si agregamos un extra ToArray()antes ToLookup()como este:
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 );
}
Este último es significativamente más rápido. Más específicamente, cuando se usa un archivo de prueba con 1,4 millones de líneas, el primero toma alrededor de 4,3 segundos y el segundo toma alrededor de 3 segundos.
Espero que ToArray()tome más tiempo, por lo que este último debería ser un poco más lento. ¿Por qué es realmente más rápido?
Información extra:
Encontramos este problema porque hay otro método que analiza el mismo archivo .csv a un formato diferente y toma alrededor de 3 segundos, por lo que creemos que este debería ser capaz de hacer lo mismo en 3 segundos.
El tipo de datos original es
Dictionary<string, List<DgvItems>>y el código original no utilizó linq y el resultado es similar.
Clase de prueba 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 );
    }
}
Resultado:
| Method |    Mean |    Error |   StdDev |
|------- |--------:|---------:|---------:|
|  First | 2.530 s | 0.0190 s | 0.0178 s |
| Second | 3.620 s | 0.0217 s | 0.0203 s |
Hice otra base de prueba en el código original. Parece que el problema no está en 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;
    }
}
Resultado:
| Method |    Mean |    Error |   StdDev |
|------- |--------:|---------:|---------:|
|  First | 2.470 s | 0.0218 s | 0.0182 s |
| Second | 3.481 s | 0.0260 s | 0.0231 s |
                
.ToArray(), la llamada a.Select( line => new DgvItems( line ) )devuelve un IEnumerable antes de la llamada aToLookup( item => item.StocksID ). Y buscar un elemento en particular es peor usando IEnumerable que Array. Probablemente más rápido para convertir a una matriz y realizar búsquedas que usar un número innumerable.var file = File.ReadLines( fileName );ReadLinesReadAllLinesBenchmarkDotnetpara medir el rendimiento real. Además, intente aislar el código real que desea medir y no incluya IO en la prueba.Respuestas:
Logré replicar el problema con el siguiente código simplificado:
Es importante que los miembros de la tupla creada sean cadenas. Eliminar los dos
.ToString()del código anterior elimina la ventaja deToArray. .NET Framework se comporta un poco diferente a .NET Core, ya que es suficiente para eliminar solo el primero.ToString()y eliminar la diferencia observada.No tengo idea de por qué sucede esto.
fuente
ToArrayoToListobliga a los datos a estar en la memoria contigua; hacer ese forzamiento en una etapa particular de la tubería, a pesar de que agrega costos, puede causar que una operación posterior tenga menos errores de caché del procesador; los errores de caché del procesador son sorprendentemente caros.