【问题标题】:This simple loop require a lot of time and I don't know why这个简单的循环需要很多时间,我不知道为什么
【发布时间】:2020-05-28 09:28:12
【问题描述】:

我在应用程序中有这个简单的循环。 IEnumerablerels中的记录是468条,IEnumerableanalyzers中的记录是78条,所以每个analyzer中有6条记录关系

  IEnumerable<Reliability> rels = new Reliability().GetReliabilities()
    .Where(m => m.StartDate >= startDate && m.EndDate <= endDate && m.ContractId == ContractId && m.JobOrderId == JobOrderId && analyzers.Any(x => x.Id == m.AnalyzerId))
    .OrderBy(m => m.StartDate);

  List<ReliabilityModel> model = new List<ReliabilityModel>();

  foreach(Analyzer analyzer in analyzers)
  {
    long rel = rels.Where(m => m.AnalyzerId == analyzer.Id).Sum(m => m.ReliabilityHoursTicks);
    long tot = rels.Where(m => m.AnalyzerId == analyzer.Id).Sum(m => m.TotalHoursTicks);
    TimeSpan relHours = TimeSpan.FromTicks(rel);
    TimeSpan totHours = TimeSpan.FromTicks(tot);
    string relTime = ((int)Math.Truncate(relHours.TotalHours)).ToString() + ":" + relHours.Minutes.ToString("0#");
    string totTime = ((int)Math.Truncate(totHours.TotalHours)).ToString() + ":" + totHours.Minutes.ToString("0#");

    decimal value = Decimal.Divide(tot - rel, tot);

    model.Add(new ReliabilityModel
    {
      AnalyzerTagName = analyzer.TagName,
      AnalyzerTypeName = analyzer.AnalyzerTypeName, 
      ContractName = rels.Select(m => m.ContractName).FirstOrDefault(),
      JobOrderName = rels.Select(m => m.JobOrderName).FirstOrDefault(), 
      ReliabilityHours = relTime,
      TotalHours = totTime,
      Value = value.ToString("P4")
    });

  }

问题是处理需要很长时间,大约 200 秒,我不明白为什么。 有什么想法吗?

【问题讨论】:

  • 我会尝试几件事:1. 将 IEnumerable rels 更改为 List rels 2. 提取 rels.Where(m => m.AnalyzerId == analyzer.Id) 成一个变量
  • 你为什么检查analyzers两次,在WhereAny?我想,这个analyzers.Any(x =&gt; x.Id == m.AnalyzerId) 会慢很多。此外,IEnumerable&lt;Reliability&gt; rels 似乎每次在循环中访问时都会被评估。您可以使用ToList() 实现它
  • 首先您想知道问题出在您的查询中还是在 foreach 中。为此,请暂时将 rels 设为 List.Reliability。这使得在执行 foreach 之前已经创建了所有 rel。衡量什么需要更多时间:制作列表或 foreach
  • 还值得问一下这是否是 EF,以及是否有机会延迟加载实体?
  • @DmitriTsoy 非常感谢。只需将 IEnumerable 更改为 List 即可,该过程在第二个之下。现在我必须检查我的所有应用程序以避免这种行为。

标签: c# linq loops model-view-controller


【解决方案1】:

性能的黄金法则是衡量。如果您必须粗粒度时间来找出问题所在,那么请获得更细粒度的时间。通过插入更多秒表或使用profiling tools

一些可能的问题:

  1. rels 可以为循环的每次迭代延迟评估(两次!)。使用 ToList 将其转换为实际列表。 (正如 cmets 中的几个人提到的)
  2. rels 在每次迭代中被分析器过滤。事先对可靠性进行分组可能会更有效。

    relsByAnalyzer = rels.GroupBy(r => r.AnalyzerId).ToDictionary(r => r.Key, r => r.ToList());

【讨论】:

  • 好的,谢谢。只需添加 ToList 即可改善时间。再次感谢您对可靠性进行分组的建议。
【解决方案2】:

简答:

问题出在

IEnumerable<Reliability> rels = new Reliability().GetReliabilities()...

最后没有 ToList()。

每个分析器您拨打GetReliabilities() 四次。可能 GetReliabilities 不只是返回一个 List 或其他东西,而是真的必须创建一个包含所有 Reliabilities 的对象。

解决方案:在开始枚举之前添加 .ToList()。这样,可靠性仅创建一次。

长答案

一个实现IEnumerable&lt;...&gt;的对象代表一个序列,可能获取序列的第一个元素,只要你有一个元素就可以得到下一个元素是下一个元素。

但请注意,枚举 IEnumerable&lt;...&gt; 可能需要一些时间,尤其是在创建枚举对象需要一些时间时。

所以首先你创建一个 IEnumerable。尚未枚举此 IEnumerable。

IEnumerable<Reliability> rels = ...

然后你开始枚举你的分析器序列:

foreach(Analyzer analyzer in analyzers)
{
    long rel = rels.Where(m => m.AnalyzerId == analyzer.Id).Sum(m => m.ReliabilityHoursTicks);

这意味着,对于第一个分析器,您创建 rels 中的所有元素,并为每个元素决定是否要保留它。从所有保留的元素中获取 ReliabilityHoursTicks 并将它们相加。

long tot = rels.Where(m => m.AnalyzerId == analyzer.Id).Sum(m => m.TotalHoursTicks);

您对 rels 进行原始查询,再次创建 rels 中的所有元素,并为每个元素...(等等)

稍后:

  ContractName = rels.Select(m => m.ContractName).FirstOrDefault(),
  JobOrderName = rels.Select(m => m.JobOrderName).FirstOrDefault(), 

根据GetReliabilities() 的智能程度,您可以创建一个 rel 元素,或者 GetReliabilites 获取您只使用其中一个的所有元素

因此,对于每个分析器,您调用 GetReliabilities 四次。那是你的瓶颈

【讨论】:

  • 感谢您的冗长回答,让我对收藏有了更多了解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-05
  • 1970-01-01
  • 2020-12-27
相关资源
最近更新 更多