为什么我的对象需要很长时间才能创建?

时间:2014-09-27 01:31:59

标签: c# performance

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

最初我的代码看起来像这样:

    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

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

我应该做些什么?

2 个答案:

答案 0 :(得分:5)

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

我记得你之前的问题,我记得看到建议使用方法组和string.Count(char.IsLetterOrDigit)并思考" yeh看起来很漂亮但是表现不佳" ,所以实际上看到你发现了那个很有趣。

如果性能很重要,我只会在没有委托期间的情况下进行,并使用一个带有单个传递的巨型循环,没有委托或多次迭代的传统方式,甚至更进一步,通过组织逻辑来调整它,以便任何情况下排除其他案件的是有组织的,以便你做懒惰的评估"。例如,如果您知道某个字符是空格,则不要检查数字或字母等。或者如果您知道它是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语句,并将最常用的字母放在" tree"的顶部,依此类推。当然,如果你非常关心速度,它可能是普通C的工作。

但是我从你原来的问题中走了一段距离。 :)

答案 1 :(得分:3)

你走过文本一次的旧方式,随着时间的推移增加了所有的计数器。以新的方式,您可以浏览文本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);

它仍然会翻阅文本一次,但许多工作人员将同时浏览文本的各个部分。