我正在编写扫描大部分文本的代码,并对其执行一些基本统计,例如大写和小写字符的数量,标点字符等。
最初我的代码看起来像这样:
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 。
这怎么可能?虽然它可能看起来不是很多额外的时间,但是当我让它运行处理文本时它很快就会增加。
我应该做些什么?
答案 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);
它仍然会翻阅文本一次,但许多工作人员将同时浏览文本的各个部分。