如何优化循环?百万级数据场景下代码运行耗时过长求优化方案
嘿,这个问题我太熟了——嵌套遍历两个大列表绝对是性能杀手,尤其是当数据量同步增长时,O(nm)的时间复杂度会直接让耗时爆炸(你3万100万就是300亿次循环操作,能快才怪)。咱们一步步来拆解优化方案,直接把耗时从分钟级压到秒级:
核心优化:把线性查找改成O(1)/O(log m)的查找
这是最关键的一步,直接解决时间复杂度问题:
1. 用哈希表(Dictionary)做精确匹配
如果你的订单和价格是通过某个唯一键精确匹配(比如商品ID、订单编号),那先把samplePriceList转换成字典:
// 假设匹配键是ProductId,把价格数据转成字典,键为ProductId,值为对应的价格实体 var priceLookup = samplePriceList.ToDictionary(p => p.ProductId, p => p);
然后遍历订单时直接查字典,不用再循环所有价格:
foreach (var order in OrderEntityCollection) { if (priceLookup.TryGetValue(order.ProductId, out var matchedPrice)) { order.MatchedPrice = matchedPrice.Price; // 其他赋值逻辑 } }
这样查找从O(m)变成O(1),整体复杂度从O(n*m)降到O(n + m),性能提升几个数量级。
2. 用排序+二分查找做范围匹配
如果匹配条件是范围(比如订单交易时间落在价格的某个时间区间内),那先对samplePriceList按匹配字段排序,然后用二分查找:
// 先按时间排序价格列表 var sortedPrices = samplePriceList.OrderBy(p => p.TimeStamp).ToList(); foreach (var order in OrderEntityCollection) { // 用二分查找找到第一个时间>=订单交易时间的价格 int index = sortedPrices.BinarySearch( order.TradeTime, Comparer<PriceEntity>.Create((p, targetTime) => p.TimeStamp.CompareTo(targetTime)) ); // BinarySearch返回负数时,取反得到插入位置,也就是第一个符合条件的元素索引 if (index < 0) index = ~index; if (index < sortedPrices.Count) { order.MatchedPrice = sortedPrices[index].Price; } }
这种情况下,查找复杂度是O(log m),整体复杂度是O(m log m + n log m),比嵌套循环快太多。
锦上添花的优化
1. 并行处理(多核利用)
如果订单之间没有依赖关系(比如不需要共享状态、不需要按顺序处理),可以用Parallel.ForEach来利用多核CPU:
// 注意:如果要修改订单实体,确保实体本身是线程安全的,或者没有并发冲突 Parallel.ForEach(OrderEntityCollection, order => { if (priceLookup.TryGetValue(order.ProductId, out var matchedPrice)) { order.MatchedPrice = matchedPrice.Price; } });
不过这步要在核心优化完成后再做,不然O(n*m)的复杂度下,并行也救不了本质的慢。
2. 数据预处理:去重+过滤
检查samplePriceList里有没有重复的无效数据,提前过滤掉不需要的条目(比如和订单无关的价格),减少查找的数据集大小。
3. 避免循环内的冗余操作
比如不要在循环里创建对象、计算重复值(比如订单的某个字段每次都重新计算),把这些操作提到循环外面。
实际效果参考
你之前测试1500条订单+30万条价格,嵌套循环是1500*30万=4.5亿次操作;改成字典查找后是1500+30万=301500次操作,耗时直接从40-50秒降到毫秒级,差距一目了然。
内容的提问来源于stack exchange,提问作者user1535623

