为什么我的对象需要很长时间才能创建
本文关键字:长时间 创建 我的 对象 为什么 | 更新日期: 2023-09-27 18:24:48
我正在编写扫描大块文本并对其进行一些基本统计的代码,如大小写字符数、标点符号数等。
最初我的代码是这样的:
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。
这怎么可能?虽然这似乎不是一个很大的额外时间,但当我让它运行处理文本时,它很快就会增加。
我应该采取什么不同的做法?
您使用的是方法组(语法糖隐藏lambda或委托),并对字符进行多次迭代,而您只需一次就可以完成(就像在原始代码中一样)。
我记得你之前的问题,我记得看到过使用方法group和string的建议。数(char.IsLetterOrDigit),然后想"是的,这看起来很漂亮,但不会表现得很好",所以看到你真的发现了这一点,真的很有趣。
如果性能很重要,我只会在没有委托周期的情况下进行,并使用一个带有单次传递的巨大循环,这是没有委托或多次迭代的传统方式,甚至可以通过组织逻辑来调整它,从而组织排除其他情况的任何情况,从而进行"懒惰评估"。例如,如果你知道一个字符是空白,那么不要检查数字或字母等。或者,如果你认为它是digitalOrAlpha,那么在该条件中包括数字和字母检查。
类似于:
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++;
...
}
现在,如果你想变得真正疯狂,根据现实生活中出现的频率组织切换语句,并将最常见的字母放在"树"的顶部,以此类推。当然,如果你那么关心速度,这可能是普通C.的工作
但我偏离了你最初的问题
按照您以前的方式遍历文本一次,在遍历过程中增加所有计数器。在你的新方式中,你浏览文本13次(每次呼叫stringToCount.Count(
一次),每次通过只更新一个计数器。
然而,这类问题是Parallel.ForEach
的完美情况。你可以用多个线程遍历文本(确保你的增量是线程安全的),并更快地获得总数。
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);
它仍然遍历文本一次,但许多工人将同时遍历文本的各个部分。