You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

1400万条数据更新性能过慢,求高效优化方案

优化1400万条记录更新的性能问题

我需要更新1400万条存储在interval_list中的记录,包括数值、位置及其他信息。每次迭代时,我会通过Interval对象的MeterID获取对应的位置信息与乘数,随后更新位置并基于乘数调整数值。当前代码运行耗时超过4小时,求更优实现方式。

原代码:

foreach (Interval interval in interval_list)
{       
    var result = from m in meterList
                 where m.MeterID.Equals(interval.MeterID)
                 where m.StartDate < (interval.UTCDateTime.ToLocalTime())
                 where m.FinalDate > (interval.UTCDateTime.ToLocalTime())
                 select m;

    if (result.Count() == 1) //Ignore if > 1
    {
        int mult1 = result.ElementAt(0).Mult1;
        int mult2 = result.ElementAt(0).Mult2;
        interval.ServiceID = result.ElementAt(0).Locserv;
                                       
        // With updating check to see if value is already adjusted by mult

        if (interval.Mult1 > 1)
        {
            interval.Value = interval.Value / interval.Mult1;
            interval.Value = interval.Value * mult1;
        }

        interval.Mult1 = mult1; //MULT1
        interval.Mult2 = mult2; //MULT2
    }
}

public class Interval
{
    public string MeterID { get; set; }
    public DateTime UTCDateTime { get; set; }
    public float Value { get; set; }
    public DateTime LocalDateTime { get; set; }
    public string ServiceID { get; set; }
    public string Account { get; set; }
    public string Rate { get; set; }
    public int Mult1 { get; set; }
    public int Mult2 { get; set; }
}

public class Meters
{
    public string MeterID { get; set; }
    public string Locserv { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime FinalDate { get; set; }
    public int Mult1 { get; set; }
    public int Mult2 { get; set; }
}

核心性能瓶颈分析

原代码的低效点集中在以下几点:

  • 线性遍历的高复杂度:每次循环都对meterList做全量线性查询,1400万次循环带来O(N*M)的时间复杂度(N为interval数量,M为meter数量),这是最大的性能杀手。
  • 重复枚举LINQ结果:Count()和ElementAt(0)会分别枚举一次查询结果,相当于对匹配的meter数据遍历两次,额外消耗资源。
  • 重复时间转换:每次循环都调用UTCDateTime.ToLocalTime(),重复的时间转换累积消耗大量CPU。

分步优化方案

1. 预构建Meter分组索引

先按MeterID对meterList分组并构建字典,将按MeterID查找的时间复杂度从O(M)降到O(1):

// 预构建分组字典:key=MeterID,value=对应Meters列表
var meterDict = meterList
    .GroupBy(m => m.MeterID)
    .ToDictionary(g => g.Key, g => g.ToList());

2. 批量转换UTC时间到本地时间

提前将所有Interval的UTC时间转换为本地时间,避免循环内重复计算:

// 批量转换,只执行一次
foreach (var interval in interval_list)
{
    interval.LocalDateTime = interval.UTCDateTime.ToLocalTime();
}

3. 优化循环内的匹配逻辑

从字典直接获取对应MeterID的列表,遍历匹配时间区间时一旦发现超过1个匹配就立即终止,同时只枚举一次结果:

foreach (Interval interval in interval_list)
{
    if (meterDict.TryGetValue(interval.MeterID, out var meters))
    {
        Meters matchedMeter = null;
        int matchCount = 0;

        foreach (var m in meters)
        {
            if (m.StartDate < interval.LocalDateTime && m.FinalDate > interval.LocalDateTime)
            {
                matchedMeter = m;
                matchCount++;
                // 超过1个匹配直接跳出,无需继续遍历
                if (matchCount > 1) break;
            }
        }

        if (matchCount == 1)
        {
            var m = matchedMeter;
            interval.ServiceID = m.Locserv;
            
            // 合并数值计算,减少一次赋值
            if (interval.Mult1 > 1)
            {
                interval.Value = interval.Value / interval.Mult1 * m.Mult1;
            }
            
            interval.Mult1 = m.Mult1;
            interval.Mult2 = m.Mult2;
        }
    }
    // 无匹配MeterID时直接跳过
}

4. 进阶优化:二分查找时间区间(可选)

如果每个MeterID下的时间区间是不重叠且按时间有序的,可以对分组后的Meters列表按StartDate排序,然后用二分查找快速定位匹配区间,进一步降低遍历成本:

// 构建字典时同时按StartDate排序
var meterDict = meterList
    .GroupBy(m => m.MeterID)
    .ToDictionary(g => g.Key, g => g.OrderBy(m => m.StartDate).ToList());

二分查找实现:

if (meterDict.TryGetValue(interval.MeterID, out var meters))
{
    int left = 0;
    int right = meters.Count - 1;
    Meters matchedMeter = null;
    int matchCount = 0;

    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        var m = meters[mid];
        
        if (m.FinalDate <= interval.LocalDateTime)
        {
            // 当前区间在目标时间之前,查找右侧
            left = mid + 1;
        }
        else if (m.StartDate >= interval.LocalDateTime)
        {
            // 当前区间在目标时间之后,查找左侧
            right = mid - 1;
        }
        else
        {
            // 找到匹配区间,检查是否有其他重叠区间
            matchedMeter = m;
            matchCount++;
            
            // 检查左侧是否有匹配
            int temp = mid - 1;
            while (temp >= 0)
            {
                var leftM = meters[temp];
                if (leftM.StartDate < interval.LocalDateTime && leftM.FinalDate > interval.LocalDateTime)
                {
                    matchCount++;
                    break;
                }
                temp--;
            }
            
            // 检查右侧是否有匹配
            temp = mid + 1;
            while (temp < meters.Count)
            {
                var rightM = meters[temp];
                if (rightM.StartDate < interval.LocalDateTime && rightM.FinalDate > interval.LocalDateTime)
                {
                    matchCount++;
                    break;
                }
                temp++;
            }
            break;
        }
    }

    if (matchCount == 1 && matchedMeter != null)
    {
        // 同前的更新逻辑
        interval.ServiceID = matchedMeter.Locserv;
        if (interval.Mult1 > 1)
        {
            interval.Value = interval.Value / interval.Mult1 * matchedMeter.Mult1;
        }
        interval.Mult1 = matchedMeter.Mult1;
        interval.Mult2 = matchedMeter.Mult2;
    }
}

5. 并行处理(多核加速)

如果interval_list中的每个Interval对象是独立无共享状态的,可以用Parallel.ForEach利用多核CPU并行处理,大幅缩短运行时间:

// 注意:确保meterDict是只读的(线程安全),且Interval对象无共享状态
Parallel.ForEach(interval_list, interval =>
{
    if (meterDict.TryGetValue(interval.MeterID, out var meters))
    {
        Meters matchedMeter = null;
        int matchCount = 0;

        foreach (var m in meters)
        {
            if (m.StartDate < interval.LocalDateTime && m.FinalDate > interval.LocalDateTime)
            {
                matchedMeter = m;
                matchCount++;
                if (matchCount > 1) break;
            }
        }

        if (matchCount == 1)
        {
            var m = matchedMeter;
            interval.ServiceID = m.Locserv;
            if (interval.Mult1 > 1)
            {
                interval.Value = interval.Value / interval.Mult1 * m.Mult1;
            }
            interval.Mult1 = m.Mult1;
            interval.Mult2 = m.Mult2;
        }
    }
});

优化效果预估

通过上述优化,时间复杂度从O(N*M)降至O(N*K)(K为单个MeterID下的平均记录数),若使用二分查找则进一步降至O(N*logK)。结合并行处理,运行时间可从4小时大幅缩短至几十分钟甚至更短。

内容的提问来源于stack exchange,提问作者BL18

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 03:40:22