【问题标题】:C# List<Match>.AddRange() very slowC# List<Match>.AddRange() 非常慢
【发布时间】:2022-01-25 23:12:57
【问题描述】:

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

我刚刚为我的项目(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, %] 自 CPU [unit, %] 模块类别 |||||||||||||||| - [外部调用] System.Collections.Generic.List.InsertRange(int, System.Collections.Generic.IEnumerable) 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 中。不需要9秒:

        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,所以我为列表预先分配了内存。现在它仍然需要 9 秒:

        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 秒。简介说 函数名称 Total CPU [unit, %] 自 CPU [unit, %] 模块类别 |||||||||||||||| - [外部调用] System.Text.RegularExpressions.MatchCollection.MatchCollection+Enumerator.MoveNext() 183804 (92.14%) 183804 (92.14%) 多模块IO |内核

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

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

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

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

【问题讨论】:

    标签: regex list visual-studio profiler addrange


    【解决方案1】:

    最后,不是 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;
    }
    

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

    【讨论】:

      猜你喜欢
      • 2017-07-01
      • 2015-04-02
      • 2015-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-14
      • 1970-01-01
      相关资源
      最近更新 更多