2014-09-27 205 views
3

我在写代码,扫描文本的大片段,并且在它执行一些基本的统计数据,如大写和小写字符数,标点符号等为什么我的对象需要很长时间才能创建?

本来我的代码是这样的:

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); 

但是在代码审查堆栈可置换用户在安吉指出我可以做以下事情。太好了,我为自己节省了一大堆代码。

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的

这怎么可能?尽管看起来不是很多额外的时间,但是当我离开它处理文本时,它很快就会加起来。

我应该做什么不同?

+5

第一种方法迭代字符串一次,第二种迭代字符串13次。字符串有多大?如果是10K以上的字符,则只需迭代所有字符就可能花费不少的时间。 – 2014-09-27 01:38:59

+0

我不知道在这里使用'LINQ'会更快吗? – rhughes 2014-09-28 14:57:10

回答

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); 

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

+0

你在'Interlocked.Increment'调用参数中缺少'ref's。 – 2014-09-27 01:52:16

+0

@KirillShlenskiy谢谢,修正,我在网站上写了这个,并没有通过编译器运行它。 – 2014-09-27 01:55:56

+0

如果这实际上比OP的原始版本更快,我会非常惊讶。 – Gabe 2014-09-27 02:13:51

5

您正在使用方法组(语法糖隐藏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++; 

    ... 
} 

现在,如果你想获得真正的疯狂,按照occurence现实生活中的频率组织switch语句,并把最常用的字母在“树”的顶部,等等。当然,如果你关心速度的话,这对于普通的C来说可能是一种工作。

但是我已经在你原来的问题上走了一段很远的距离。 :)

相关问题