我正在使用EF核心从SQLite中读取40,000个小对象/行,这需要18秒,这对我的UWP应用来说太长了。 当发生这种情况时,单个核心上的CPU使用率达到100%,但磁盘读取速度大约为1%。
var dataPoints = _db.DataPoints.AsNoTracking().ToArray();
如果没有AsNoTracking()
,所用时间会更长。
DataPoint
是一个具有一些原始属性的小型POCO。我加载的数据总量为4.5 MB。
public class DataPointDto
{
[Key]
public ulong Id { get; set; }
[Required]
public DateTimeOffset TimeStamp { get; set; }
[Required]
public bool trueTime { get; set; }
[Required]
public double Value { get; set; }
}
问题:是否有更好的方法来加载这么多对象,还是我坚持这种性能水平?
有趣的事实: x86需要11秒,x64需要18秒。“优化代码”会缩短一秒钟。使用Async
将执行时间推迟到30秒。
答案 0 :(得分:5)
大多数答案都遵循加载较少数据的常识,但在某些情况下,例如此处绝对肯定必须加载大量实体。那我们该怎么做呢?
表现不佳的原因
此操作难道不可避免地需要这么长时间吗? 好吧,不是。我们只从磁盘加载一兆字节的数据,性能不佳的原因是数据被分成40,000个小实体。数据库可以处理,但实体框架似乎很难设置所有这些实体,更改跟踪等。如果我们不打算修改数据,我们可以做很多事情。
只加载一个属性,然后获得基元列表。
List<double> dataPoints = _db.DataPoints.Select(dp => dp.Value).ToList();
这绕过了通常由实体框架执行的所有实体创建。此查询耗时0.4秒,而原始查询则为18秒。我们正在谈论 45(!)次改进。
当然,大多数时候我们需要的不仅仅是一系列基元 我们可以在LINQ查询中创建新对象。实体框架不会创建它通常会实现的实体,并且操作运行得更快。为方便起见,我们可以使用匿名对象。
var query = db.DataPoints.Select(dp => new {Guid ID = dp.sensorID, DateTimeOffset Timestamp = dp.TimeStamp, double Value = dp.Value});
此操作需要1.2秒,而正常检索相同数量的数据需要18秒。
我发现在我的情况下,使用Tuples而不是匿名类型可以稍微提高性能,以下查询的执行速度提高了大约30%:
var query = db.DataPoints.Select(dp => Tuple.Create(dp.sensorID, dp.TimeStamp, dp.Value));
答案 1 :(得分:2)
您可以使用其他技术加载所有商品。
当用户滚动ListView时,你可以创建自己的逻辑来加载部分数据(我想你正在使用它)。
幸运的是,UWP是一种简单的方法来实现这一技术。 增量加载 请参阅文档和示例答案 2 :(得分:0)
对EF Core 3.1.5的2600万条记录(1个日期时间,1个双精度,1个整数)进行性能测试:
接受的答案中建议的匿名类型或元组=关于 20秒,1.3GB RAM
结构=大约15秒,0.8GB RAM