【问题标题】:Why does my object take a long time to be created?为什么我的对象需要很长时间才能创建?
【发布时间】:2014-09-27 10:06:17
【问题描述】:

我正在编写扫描大段文本并对其进行一些基本统计的代码,例如大小写字符的数量、标点符号等。

原来我的代码是这样的:

    foreach (var character in stringToCount)
    {
        if (char.IsControl(character))
        {
            controlCount++;
        }
        if (char.IsDigit(character))
        {
            digitCount++;
        }
        if (char.IsLetter(character))
        {
            letterCount++;
        } //etc.
    }

然后我从那里创建了一个像这样的新对象,它只是读取局部变量并将它们传递给构造函数:

    var result = new CharacterCountResult(controlCount, highSurrogatecount, lowSurrogateCount, whiteSpaceCount,
        symbolCount, punctuationCount, separatorCount, letterCount, digitCount, numberCount, letterAndDigitCount,
        lowercaseCount, upperCaseCount, tempDictionary);

但是,Code Review Stack Exchange 上的一位用户指出,我可以执行以下操作。太好了,我为自己节省了大量代码,这很好。

    var result = new CharacterCountResult(stringToCount.Count(char.IsControl),
        stringToCount.Count(char.IsHighSurrogate), stringToCount.Count(char.IsLowSurrogate),
        stringToCount.Count(char.IsWhiteSpace), stringToCount.Count(char.IsSymbol),
        stringToCount.Count(char.IsPunctuation), stringToCount.Count(char.IsSeparator),
        stringToCount.Count(char.IsLetter), stringToCount.Count(char.IsDigit),
        stringToCount.Count(char.IsNumber), stringToCount.Count(char.IsLetterOrDigit),
        stringToCount.Count(char.IsLower), stringToCount.Count(char.IsUpper), tempDictionary);

但是第二种方式创建对象大约需要(在我的机器上)额外的 ~200ms

这怎么可能?虽然它可能看起来不是很多额外的时间,但当我让它运行处理文本时它很快就会加起来。

我应该做些什么不同的事情?

【问题讨论】:

  • 第一种方式迭代字符串一次,第二种方式迭代字符串13次。字符串有多大?如果它是 10K+ 个字符,则仅迭代所有字符可能需要大量时间。
  • 不知道在这里使用LINQ会不会更快?

标签: c# performance


【解决方案1】:

您正在使用方法组(隐藏 lambda 或委托的语法糖)并多次迭代字符,而您可以通过一次完成它(就像在您的原始代码中一样)。

我记得你之前的问题,我记得看到使用方法组和 string.Count(char.IsLetterOrDigit) 的建议并认为“是的,看起来很漂亮但性能不佳”,所以实际上很有趣看到你确实找到了。

如果性能很重要,我会在没有委托期间的情况下进行,并使用单次传递的巨型循环,传统方式没有委托或多次迭代,甚至更进一步,通过组织逻辑来调整它,以便在任何情况下排除其他情况的组织方式使您可以进行“惰性评估”。例如,如果您知道一个字符是空格,则不要检查数字或字母等。或者如果您知道它是 digitOrAlpha,则在该条件中包含数字和字母检查。

类似:

foreach(var ch in string) {
   if(char.IsWhiteSpace(ch)) {
      ...
   }
   else {
      if(char.IsLetterOrDigit(ch)) {
         letterOrDigit++;
         if(char.IsDigit(ch)) digit++;
         if(char.IsLetter(ch)) letter++;
      }  
   }
}

如果您真的想进行微优化,请编写一个程序来预先计算所有选项并发出一个巨大的 switch 语句来进行表查找。

switch(ch) {
   case 'A':
        isLetter++;
        isUpper++;
        isLetterOrDigit++;
        break;
   case 'a':
        isLetter++;
        isLower++;
        isLetterOrDigit++;
        break;
   case '!':
        isPunctuation++;

   ...
}

现在,如果您想真正疯狂,请根据现实生活中的出现频率组织 switch 语句,并将最常见的字母放在“树”的顶部,依此类推。当然,如果你这么在意速度,那可能是普通 C 的工作。

但是我离你原来的问题有点远了。 :)

【讨论】:

    【解决方案2】:

    你曾经浏览过文本的旧方式,随着你去增加你所有的计数器。在您的新方式中,您遍历文本 13 次(每次调用 stringToCount.Count( 一次)并且每次仅更新一个计数器。

    但是,这种问题对于Parallel.ForEach 来说是完美的情况。您可以使用多个线程浏览文本(确保您的 increments are thread safe)并更快地获得总数。

    Parallel.ForEach(stringToCount, character =>
    {
        if (char.IsControl(character))
        {
            //Interlocked.Increment gives you a thread safe ++
            Interlocked.Increment(ref controlCount);
        }
        if (char.IsDigit(character))
        {
            Interlocked.Increment(ref digitCount);
        }
        if (char.IsLetter(character))
        {
            Interlocked.Increment(ref letterCount);
        } //etc.
    });
    
    var result = new CharacterCountResult(controlCount, highSurrogatecount, lowSurrogateCount, whiteSpaceCount,
        symbolCount, punctuationCount, separatorCount, letterCount, digitCount, numberCount, letterAndDigitCount,
        lowercaseCount, upperCaseCount, tempDictionary);
    

    它仍然遍历文本一次,但许多工作人员会同时遍历文本的各个部分。

    【讨论】:

    • 您在Interlocked.Increment 调用参数中缺少refs。
    • @KirillShlenskiy 谢谢,修复,我在网站上写了这个,并没有通过编译器运行它。
    • 如果这实际上比 OP 的原始版本更快,我会感到非常惊讶。
    • @Gabe 这取决于文本的长度。他确实说过“大段文字”,如果是几 K 的文字,我相信会有很大的改进。
    • 我用 Moby Dick (1.2MB) 作为“大段文本”进行了尝试。在 4 个线程上运行的并行版本在字符串长度低至约 10k 的情况下速度几乎是原来的两倍,而在只有几个字符的字符串上速度则高达 200 倍。
    猜你喜欢
    • 1970-01-01
    • 2020-11-08
    • 2015-09-17
    • 2022-11-05
    • 2015-08-29
    • 2012-05-15
    • 2016-10-03
    • 2020-02-28
    • 2013-02-06
    相关资源
    最近更新 更多