У нас есть короткий метод, который анализирует файл .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()
должно занять дополнительное время, поэтому последнее должно быть немного медленнее. Почему это на самом деле быстрее?
Дополнительная информация:
Мы обнаружили эту проблему, потому что есть другой метод, который анализирует один и тот же файл .csv в другом формате, и это занимает около 3 секунд, поэтому мы считаем, что этот способ сможет сделать то же самое за 3 секунды.
Исходный тип данных:
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 |
.ToArray()
, вызов.Select( line => new DgvItems( line ) )
возвращает IEnumerable перед вызовомToLookup( item => item.StocksID )
. И поиск определенного элемента хуже с использованием IEnumerable, чем Array. Вероятно, быстрее преобразовать в массив и выполнить поиск, чем при использовании ienumerable.var file = File.ReadLines( fileName );
-ReadLines
вместо,ReadAllLines
и ваш код, вероятно, будет быстрееBenchmarkDotnet
для фактического измерения производительности. Кроме того, попробуйте выделить реальный код, который вы хотите измерить, и не включать IO в тест.Ответы:
Мне удалось воспроизвести проблему с упрощенным кодом ниже:
Важно, чтобы члены созданного кортежа были строками. Удаление двух
.ToString()
из приведенного выше кода исключает преимуществоToArray
. .NET Framework ведет себя немного иначе, чем .NET Core, поскольку достаточно удалить только первое,.ToString()
чтобы устранить наблюдаемую разницу.Я понятия не имею, почему это происходит.
источник
ToArray
илиToList
заставляют данные находиться в непрерывной памяти; выполнение этого форсирования на определенной стадии конвейера, даже если это увеличивает стоимость, может привести к тому, что в более поздней операции будет меньше пропусков кэша процессора; промахи кеша процессора удивительно дороги.