2014-09-27 140 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來說可能是一種工作。

但是我已經在你原來的問題上走了一段很遠的距離。 :)

相關問題