Mamy krótką metodę, która analizuje plik .csv do odnośnika:
ILookup<string, DgvItems> ParseCsv( string fileName )
{
var file = File.ReadAllLines( fileName );
return file.Skip( 1 ).Select( line => new DgvItems( line ) ).ToLookup( item => item.StocksID );
}
I definicja 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] );
}
}
I stwierdziliśmy, że jeśli dodamy ToArray()
wcześniej ToLookup()
taki dodatek :
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 );
}
Ten ostatni jest znacznie szybszy. Mówiąc dokładniej, gdy używasz pliku testowego zawierającego 1,4 miliona linii, pierwszy zajmuje około 4,3 sekundy, a drugi około 3 sekund.
Spodziewam się, że ToArray()
powinno to zająć więcej czasu, więc ta ostatnia powinna być nieco wolniejsza. Dlaczego faktycznie jest szybszy?
Dodatkowe informacje:
Znaleźliśmy ten problem, ponieważ istnieje inna metoda, która analizuje ten sam plik .csv do innego formatu i zajmuje to około 3 sekund, więc uważamy, że ten powinien być w stanie zrobić to samo w ciągu 3 sekund.
Oryginalny typ danych jest,
Dictionary<string, List<DgvItems>>
a oryginalny kod nie używał linq, a wynik jest podobny.
Klasa testowa 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 );
}
}
Wynik:
| Method | Mean | Error | StdDev |
|------- |--------:|---------:|---------:|
| First | 2.530 s | 0.0190 s | 0.0178 s |
| Second | 3.620 s | 0.0217 s | 0.0203 s |
Zrobiłem kolejną bazę testową na oryginalnym kodzie. Wydaje się, że problem nie dotyczy 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;
}
}
Wynik:
| 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()
wywołania.Select( line => new DgvItems( line ) )
zwraca IEnumerable przed wywołaniem doToLookup( item => item.StocksID )
. A wyszukiwanie konkretnego elementu jest gorsze przy użyciu IEnumerable niż Array. Prawdopodobnie szybciej przekonwertować na tablicę i wykonać wyszukiwanie niż przy użyciu niepoliczalnego.var file = File.ReadLines( fileName );
-ReadLines
zamiastReadAllLines
i prawdopodobnie kod będzie szybszyBenchmarkDotnet
do rzeczywistego pomiaru perf. Spróbuj także wyizolować rzeczywisty kod, który chcesz zmierzyć, i nie uwzględniaj we / wy testu.Odpowiedzi:
Udało mi się odtworzyć problem z poniższym uproszczonym kodem:
Ważne jest, aby członkowie utworzonej krotki były łańcuchami. Usunięcie dwóch
.ToString()
z powyższego kodu eliminuje zaletęToArray
. .NET Framework zachowuje się nieco inaczej niż .NET Core, ponieważ wystarczy usunąć tylko pierwszy,.ToString()
aby wyeliminować zaobserwowaną różnicę.Nie mam pojęcia, dlaczego tak się dzieje.
źródło
ToArray
lubToList
wymuszają przechowywanie danych w ciągłej pamięci; wykonanie tego wymuszania na określonym etapie potoku, nawet jeśli powoduje to wzrost kosztów, może spowodować, że późniejsza operacja będzie miała mniej błędów pamięci podręcznej procesora; Błędy w pamięci podręcznej procesora są zaskakująco drogie.