先看效果
为了查看慢的地方,添加了一些日志记录。其中遍历1w次便会输出一次时间。
优化前
优化后
优化后
背景
- 报表的查询导出功能,查询导出使用同一方法获取数据。
- 页面分页查询耗时还能接收,小数据量导出也能接受。
- 但是导出数据达到10w级别变得没法接受了。
- 查看服务器cup使用率一直非常高(双核服务器,cup使用率一直高于50%)。
初步分析
服务器cpu使用高,很可能是数据已经获取到,计算机一直处于快速计算中。从前面日志也证实了这点。
代码分析
``
result.ForEach(item =>
{
// 省略一部分,简单的判断和赋值
var trackingList = wobTrackingList.Where(m => m.WOBShipOrderID == item.OrderId);
if (trackingList.IsStrictSafed())
{
item.ParentTrackingNo = trackingList.FirstOrDefault(m => m.IsParentTrackingNo)?.TrackingNo;
var trackNos= trackingList.Where(m => !m.IsParentTrackingNo).Select(m=>m.TrackingNo);
item.TrackingNo = string.Join(",", trackNos);
}
// 省略一部分,简单的判断和字典中取值。
// 省略一部分,字符序列化成对象然后取值赋值。前面优化后,进行测试,证明这里也不影响处理速度。
});
``
其中 wobTrackingList也是提前查询出来的。看似都在内存中计算,应该会很快的。但是忽略了两点。1、Where 运算实际上是查询运算,虽然别人给我们封装好了,单次调用速度很快,但是也比赋值加减运算要慢。2、wobTrackingList中的数据量很可能比外层遍历的数据还要大。wobTrackingList数量越大,Where耗时越多。加之外圈遍历次数多了,累计耗时就上去了。
优化方案
将wobTrackingList 转化成字典。循环中只做取值赋值操作。
var trackingDic = new Dictionary<Guid, (string parentTrackingNo, string trackNos)>();
wobTrackingList.GroupBy(m => m.WOBShipOrderID).ToList().ForEach(t => {
var parentTrackingNo = t.FirstOrDefault(m => m.IsParentTrackingNo)?.TrackingNo;
var trackNos = string.Join(",", t.Where(m => !m.IsParentTrackingNo).Select(m => m.TrackingNo));
trackingDic.Add(t.Key, (parentTrackingNo, trackNos));
});
result.ForEach(item =>
{
if (trackingDic.ContainsKey(item.OrderId))
{
var (parentTrackingNo, trackNos) = trackingDic[item.OrderId];
item.ParentTrackingNo = parentTrackingNo;
item.TrackingNo = trackNos;
}
});
思考
- 出现效率问题的地方,多数都和循环处理数据有关。
- 尽量减少循环中的复杂逻辑,只做简单的取值赋值,判断和计算。
- 循环中处理数据,特别是耗时严重的操作(如查询数据库),要考虑好循环可能出现的次数。
- 循环中内存取值,用字典要比list效率高。
- 面对可能出现1w次循环的地方一定要多思考,多检查代码。