【发布时间】:2019-11-21 18:46:03
【问题描述】:
我们有一个将 .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] );
}
}
我们发现如果我们在ToLookup() 之前添加一个额外的ToArray(),如下所示:
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() 应该需要额外的时间,所以后者应该会稍微慢一些。为什么它实际上更快?
额外信息:
我们发现了这个问题,因为还有另一种方法可以将相同的 .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 ) )的调用会在调用ToLookup( item => item.StocksID )之前返回一个IEnumerable。使用 IEnumerable 查找特定元素比使用 Array 更糟糕。转换为数组并执行查找可能比使用 ienumerable 更快。 -
旁注:使用
var file = File.ReadLines( fileName );-ReadLines而不是ReadAllLines,你的代码可能会更快 -
您应该使用
BenchmarkDotnet进行实际性能测量。此外,请尝试隔离您要测量的实际代码,并且不要在测试中包含 IO。 -
我不知道为什么这会遭到反对 - 我认为这是个好问题。