【问题标题】:Improving performance of loop by using different list types通过使用不同的列表类型来提高循环的性能
【发布时间】:2021-11-04 00:52:57
【问题描述】:

我有以下算法适用于较小的日期范围,但是如果我将日期范围增加到大约一年(startDateendDate),它显然会降低性能,因为我每天每一分钟都在循环,是有没有办法通过使用不同的列表类型(例如哈希集或字典)来提高性能,或者还有其他我不知道的后备方法吗?

listWorkTime 包含大约 300 多个条目,其中某些日期范围可能重叠或相同但具有不同的 TimeRangeId

private List<DateSharedWork> CalculateDateSharedWork(DateTime startDate,
    DateTime endDate, ICollection<WorkTime> listWorkTime)
{
    List<DateSharedWork> listDateSharedWork = new List<DateSharedWork>();

    // +1 to include last day at full
    int range = endDate.Subtract(startDate).Days + 1;

    // start at startDate
    Parallel.For(0, range, i =>
    {
        DateTime currDate = startDate.AddDays(i);

        //set minute interval
        double everyNMinutes = 1.0;
        double minutesADay = 1440.0;

        // reset counter
        int work_counter = 0;
        int lowWork_counter = 0;
        int noWork_counter = 0;

        int l = (int)(minutesADay / everyNMinutes);

        for (int j = 0; j < l; j++)
        {
            DateTime check15 = currDate.AddMinutes(j * everyNMinutes);

            // check if listWorkTime includes current date
            var foundTime = listWorkTime
                .Where(x => check15 >= x.FromDate && check15 <= x.ToDate).ToList();

            if (foundTime.Count(x => x.TimeRangeId == 1) > 0)
            {
                // found interval that is within work hours
                work_counter++;
                noWork_counter++;
            }
            else
            {
                if (foundTime.Count(x => x.TimeRangeId == 2) > 0)
                {
                    // found intervall that is within low work hours
                    lowWork_counter++;
                    noWork_counter++;
                }
            }
        };

        double work = everyNMinutes / minutesADay * work_counter;
        double lowWork = everyNMinutes / minutesADay * lowWork_counter;
        double noWork = 1.0 - (everyNMinutes / minutesADay * noWork_counter);

        listDateSharedWork.Add(new DateSharedWork(currDate, work, lowWork, noWork));
    });

    listDateSharedWork.Sort((x, y) => DateTime.Compare(x.Date, y.Date));

    return listDateSharedWork;
}

编辑*

类定义

public class DateSharedWork
  {

    public DateSharedWork(DateTime date, double? work = 0.0, double? lowWork = 0.0, double? noWork = 1.0)
    {
      this.Date = date;
      this.Work = work.Value;
      this.LowWork = lowWork.Value;
      this.NoWork = noWork.Value;
    }

    public DateTime Date { get; private set; }
    public double Work { get; private set; }
    public double LowWork { get; private set; }
    public double NoWork { get; private set; }
  }

【问题讨论】:

  • 能否请您也发布您的DateSharedWork 类型?
  • 附带说明请注意List&lt;T&gt; 类不是线程安全的。从多个线程并行调用listDateSharedWork.Add 方法可能会导致内部状态损坏。
  • 首先使用Where()过滤输入列表,然后您是否尝试使用GroupBy()对输入进行分组,如@987654332 @?完成后,您已经知道输出列表中的项目数(预分配)并简单地遍历每个存储桶。另请注意,您不能对 minutesADay 之类的内容进行硬编码(时间变化...)

标签: c# list performance loops dictionary


【解决方案1】:

我会尝试在 DateSharedWork 中添加一个 uint 来存储一个整数作为日期的表示

即: 09-07-2021-10:05:12.13546 => 202109071005

您可以在 DateSharedWork 构造函数中添加此 uint 字段的计算。加载数据肯定会付出代价。 或者您可以尝试在底层数据库中添加字段和计算,并在执行 upsert 时执行计算。

最后,我认为它可能会提高您代码中的查询性能。至少我已经在 Data Cube 性能提示中看到了这种方法。

【讨论】:

    【解决方案2】:

    您可以通过将搜索限制为仅与currDate 相关的这些WorkTime 实例来加快foundTime 列表的计算。为此,您必须首先构建一个Dictionary,其中DateTime 作为键,List&lt;WorkTime&gt; 作为值:

    Dictionary<DateTime, List<WorkTime>> perDay = new();
    foreach (var workTime in listWorkTime)
    {
        for (var d = workTime.FromDate.Date; d < workTime.ToDate.Date.AddDays(1); d.AddDays(1))
        {
            if (!perDay.TryGetValue(d, out var list)) perDay.Add(d, list = new());
            list.Add(workTime);
        }
    }
    

    这构成了在开始计算之前必须完成的额外工作,但希望它能够加快计算速度以补偿初始成本。

    然后你就可以替换这个了:

    var foundTime = listWorkTime
        .Where(x => check15 >= x.FromDate && check15 <= x.ToDate).ToList();
    

    有了这个:

    List<WorkTime> foundTime;
    if (perDay.TryGetValue(currDate, out List<WorkTime> currDateList))
    {
        foundTime = currDateList
            .Where(x => check15 >= x.FromDate && check15 <= x.ToDate)
            .ToList();
    }
    else
    {
        foundTime = new();
    }
    

    我还建议将Parallel.For 循环替换为普通的for 循环。并行编程有很多陷阱,随时准备让粗心大意的人措手不及。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-26
      • 2019-11-24
      • 1970-01-01
      • 1970-01-01
      • 2019-01-12
      • 2021-02-22
      • 2020-08-25
      相关资源
      最近更新 更多