0

(问题已解决。请参阅下面的答案。)

我刚刚为我的项目(winform / C#)做了一个配置文件,因为我觉得它的工作速度比以前慢得多。奇怪的是 List.AddRange() 花费了整个分析过程的 92%。

Code1:使用以下代码,完成一次扫描作业需要 2m30s(非 profiling 模式):

        var allMatches = new List<Match>();
        foreach (var typedRegex in Regexes)
        {
            var ms = typedRegex.Matches(text); //typedRegex is just Regex.
            allMatches.AddRange(ms);
        }

函数名称 Total CPU [unit, %] Self CPU [unit, %] 模块类别 |||||||||||||||| - [外部调用] System.Collections.Generic.List.InsertRange(int, System.Collections.Generic.IEnumerable<!0>) 146579 (92.45%) 146579 (92.45%) 多个模块 IO | 核心

Code2:所以我去掉了AddRange,它只需要1.6s:

        var allMatches = new List<Match>();
        foreach (var typedRegex in Regexes)
        {
            var ms = typedRegex.Matches(text);
            // allMatches.AddRange(ms);
        }

Code3:考虑到可能存在某种“延迟加载”机制,我添加了一个计数器来触发 Regex.Maces()。并且计数器的值显示在 UI 中。不需要9s:

        public static int Count = 0;
        var allMatches = new List<Match>();
        foreach (var typedRegex in Regexes)
        {
            var ms = typedRegex.Matches(text);
            // allMatches.AddRange(ms);
            Count += ms.Count;
        }

Code4:注意到 Count 的值为 32676,所以我为列表预先分配了内存。现在它仍然花费 9s:

        public static int Count = 0;
        var allMatches = new List<Match>(33000);
        foreach (var typedRegex in Regexes)
        {
            var ms = typedRegex.Matches(text);
            // allMatches.AddRange(ms);
            Count += ms.Count;
        }

Code5:思考 List.AddRange(MatchCollection) 可能听起来很奇怪,我将代码更改为 foreach(...) {List.Add(match)},但什么也没发生,2 分 30 秒。配置文件显示 Function Name Total CPU [unit, %] Self CPU [unit, %] Module Category ||||||||||||||| - [外部调用] System.Text.RegularExpressions.MatchCollection.MatchCollection+Enumerator.MoveNext() 183804 (92.14%) 183804 (92.14%) 多模块IO | 核心

Code6:SelectMany 也需要 2m30s。这是我最古老的解决方案。

    var allMatches = Regexes.SelectMany(i => i.Matches(text)); 

所以,也许创建一个多达 32676 个项目的列表是一件大事,但比创建这些 Match 多 10 倍是超乎想象的。仅在 1 天前完成这项工作需要 27 秒。我今天做了很多更改,并认为分析器会告诉我原因。但它没有。AddRange() 1 个月前就在那里。我几乎记不起它以前从任何个人资料中的名字。

我会尽量记住白天发生的事情。但是有人可以解释上面的配置文件结果吗?谢谢你的帮助。

4

1 回答 1

0

最后,不是 AddRange() 的问题,而是 Regex.Matches() 的问题。在我优化了正则表达式之后,时间成本从 2 分 30 秒下降到了 11 秒以下。

首先, Regex.Matches() 是使用某种延迟加载(和多线程)。这就是它返回 MatchCollection 而不是普通列表的原因。MatchCollection 仅在您使用项目时创建项目。

MatchCollection.Count() 的成本低于 ToArray(),就像 IEnumerable.Count() 的成本低于 IEnumerable.ToArray()(收集的垃圾更少?)。

这是来自 MatchCollection 的代码:

private Match GetMatch(int i)
{
  if (this._matches.Count > i)
    return this._matches[i];
  if (this._done)
    return (Match) null;
  Match match;
  do
  {
    match = this._regex.Run(false, this._prevlen, this._input, 0, this._input.Length, this._startat);
    if (!match.Success)
    {
      this._done = true;
      return (Match) null;
    }
    this._matches.Add(match);
    this._prevlen = match.Length;
    this._startat = match._textpos;
  }
  while (this._matches.Count <= i);
  return match;
}

而且它太懒了,如果你要求第二个项目,它永远不会在第三个项目上起作用。

于 2021-12-27T07:47:42.923 回答