2010-06-25 184 views
29

一位顧問昨天來了,不知何故字符串的話題出現了。他提到他注意到,對於小於一定長度的字符串,Contains實際上比StartsWith更快。我不得不用自己的兩隻眼睛看它,所以我寫了一個小應用程序,果然,Contains更快!包含比StartsWith更快嗎?

這怎麼可能?

DateTime start = DateTime.MinValue; 
DateTime end = DateTime.MinValue; 
string str = "Hello there"; 

start = DateTime.Now; 
for (int i = 0; i < 10000000; i++) 
{ 
    str.Contains("H"); 
} 
end = DateTime.Now; 
Console.WriteLine("{0}ms using Contains", end.Subtract(start).Milliseconds); 

start = DateTime.Now; 
for (int i = 0; i < 10000000; i++) 
{ 
    str.StartsWith("H"); 
} 
end = DateTime.Now; 
Console.WriteLine("{0}ms using StartsWith", end.Subtract(start).Milliseconds); 

輸出:

726ms using Contains 
865ms using StartsWith 

我和長字符串也試了一下!

+2

兩件事。嘗試切換順序以查看它是否會影響結果。然後,由於這是一個特定於實現的問題,如果需要,請通過Reflector查看源代碼。很可能'Contains'被更仔細地優化(可能使用本地代碼),因爲它更經常被使用。 – 2010-06-25 17:30:06

+5

微觀優化很少有用。比較一串最長爲20個字符左右的字符串,並進行超過1000萬次迭代,並節省大約140ms。嘗試使用更長的字符串或更有效的用例,並查看是否獲得相同的數字。 – Chris 2010-06-25 17:33:42

+10

您的時間測量存在缺陷。您應該使用秒錶對象來跟蹤時間,而不是日期時間。如果你打算使用日期時間,你至少應該使用end.Subtract(start).TotalMilliseconds – 2010-06-25 17:35:47

回答

25

嘗試使用StopWatch來測量速度,而不是DateTime檢查。

Stopwatch vs. using System.DateTime.Now for timing events

我認爲關鍵是粗體以下重要部件:

Contains

此方法執行一個 (區分大小寫和 培養 - 不敏感)比較。

StartsWith

此方法執行使用當前區域性一個 (區分大小寫和文化敏感) 比較。

我認爲主要是序號比較這相當於:

的序號排序比較對字符串中的每個字符 對象的數值基於 字符串。序號 自動比較 區分大小寫,因爲小寫字母 和字符 的大寫版本具有不同的代碼點。但是,如果在您的 應用程序中案例不重要,則可以指定一個忽略大小寫的序號比較 。 這相當於將 字符串轉換爲大寫,使用 不變式培養,然後對結果執行 的序數比較。

參考文獻:

http://msdn.microsoft.com/en-us/library/system.string.aspx

http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx

http://msdn.microsoft.com/en-us/library/baketfxw.aspx

使用反射器,你可以看到兩個代碼:

public bool Contains(string value) 
{ 
    return (this.IndexOf(value, StringComparison.Ordinal) >= 0); 
} 

public bool StartsWith(string value, bool ignoreCase, CultureInfo culture) 
{ 
    if (value == null) 
    { 
     throw new ArgumentNullException("value"); 
    } 
    if (this == value) 
    { 
     return true; 
    } 
    CultureInfo info = (culture == null) ? CultureInfo.CurrentCulture : culture; 
    return info.CompareInfo.IsPrefix(this, value, 
     ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); 
} 
+9

是的!這是對的。正如Daniel在另一條評論中指出的那樣,將StringComparison.Ordinal傳遞給StartsWith將使StartsWith比Contains更快。我剛剛嘗試過並得到了 「748.3209ms使用Contains 154.548ms使用StartsWith」 – StriplingWarrior 2010-06-25 18:18:55

+0

@StriplingWarrior,秒錶對於短流程不可靠。每次測試都會有變化。獲得748 vs 154 ...沒有足夠的證據!所以問題是,你嘗試了多少次短程序測試? – usefulBee 2016-09-28 16:22:29

+1

@usefulBee:原始問題的代碼重複千萬次的方法調用,這使我們進入了數百毫秒。這通常足以在不涉及I/O時平滑變化。 [這是一個LINQPad腳本](http://share.linqpad.net/k7n66x.linq),它在一個更強大的基準測試平臺上顯示出類似的結果。 – StriplingWarrior 2016-09-29 15:43:00

22

我想通了。這是因爲StartsWith是文化敏感的,而Contains不是。這固有意味着StartsWith必須做更多的工作。

FWIW,這裏有我在Mono結果與以下(校正)基準:

1988.7906ms using Contains 
10174.1019ms using StartsWith 

我很高興看到MS人的結果,但我的主要觀點是,正確地完成(假定類似的優化),我認爲StartsWith有慢:

using System; 
using System.Diagnostics; 

public class ContainsStartsWith 
{ 
    public static void Main() 
    { 
     string str = "Hello there"; 

     Stopwatch s = new Stopwatch(); 
     s.Start(); 
     for (int i = 0; i < 10000000; i++) 
     { 
      str.Contains("H"); 
     } 
     s.Stop(); 
     Console.WriteLine("{0}ms using Contains", s.Elapsed.TotalMilliseconds); 

     s.Reset(); 
     s.Start(); 
     for (int i = 0; i < 10000000; i++) 
     { 
      str.StartsWith("H"); 
     } 
     s.Stop(); 
     Console.WriteLine("{0}ms using StartsWith", s.Elapsed.TotalMilliseconds); 

    } 
} 
+2

真的很好猜,但可能不會。他沒有傳遞文化,這條線在StartsWith的實現中:'CultureInfo info =(culture == null)? CultureInfo.CurrentCulture:culture;' – 2010-06-25 17:40:27

+2

@Marc Bollinger - 你所展示的就是StartsWith是文化敏感的,這就是主張。 – Lee 2010-06-25 17:46:43

+0

@馬克,對。它使用當前的文化。這是文化敏感的,一些文化依賴相當複雜的規範化規則。 – 2010-06-25 17:46:58

2

我在反射重組後四周,發現一個潛在的答案:

包含:

return (this.IndexOf(value, StringComparison.Ordinal) >= 0); 

StartsWith:

... 
    switch (comparisonType) 
    { 
     case StringComparison.CurrentCulture: 
      return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 

     case StringComparison.CurrentCultureIgnoreCase: 
      return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 

     case StringComparison.InvariantCulture: 
      return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 

     case StringComparison.InvariantCultureIgnoreCase: 
      return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 

     case StringComparison.Ordinal: 
      return ((this.Length >= value.Length) && (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0)); 

     case StringComparison.OrdinalIgnoreCase: 
      return ((this.Length >= value.Length) && (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0)); 
    } 
    throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType"); 

還有一些重載以便默認文化的CurrentCulture。

因此,首先,Ordinal會更快(如果字符串接近開頭),對吧?其次,這裏有更多的邏輯可能會減慢速度(儘管這麼瑣碎)

+1

我不同意'CultureInfo.CurrentCulture.CompareInfo.IsPrefix'是微不足道的。 – 2010-06-25 17:48:09

+0

+1 - 我沒有真正閱讀它,說實話,我只是指代碼的數量;) – hackerhasid 2010-06-25 20:04:45

9

StartsWithContains當涉及到文化敏感問題時,其行爲會完全不同。

特別是,StartsWith返回true並不意味着Contains返回true。只有當你真的知道你在做什麼時,你才應該將其中一個替換成另一個。

using System; 

class Program 
{ 
    static void Main() 
    { 
     var x = "A"; 
     var y = "A\u0640"; 

     Console.WriteLine(x.StartsWith(y)); // True 
     Console.WriteLine(x.Contains(y)); // False 
    } 
} 
0

讓我們來看看什麼ILSpy說,關於這兩個...

public virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options) 
{ 
    if (source == null) 
    { 
     throw new ArgumentNullException("source"); 
    } 
    if (value == null) 
    { 
     throw new ArgumentNullException("value"); 
    } 
    if (startIndex > source.Length) 
    { 
     throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); 
    } 
    if (source.Length == 0) 
    { 
     if (value.Length == 0) 
     { 
      return 0; 
     } 
     return -1; 
    } 
    else 
    { 
     if (startIndex < 0) 
     { 
      throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index")); 
     } 
     if (count < 0 || startIndex > source.Length - count) 
     { 
      throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count")); 
     } 
     if (options == CompareOptions.OrdinalIgnoreCase) 
     { 
      return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase); 
     } 
     if ((options & ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth)) != CompareOptions.None && options != CompareOptions.Ordinal) 
     { 
      throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options"); 
     } 
     return CompareInfo.InternalFindNLSStringEx(this.m_dataHandle, this.m_handleOrigin, this.m_sortName, CompareInfo.GetNativeCompareFlags(options) | 4194304 | ((source.IsAscii() && value.IsAscii()) ? 536870912 : 0), source, count, startIndex, value, value.Length); 
    } 
} 

看起來它認爲文化爲好,但拖欠。

public bool StartsWith(string value, StringComparison comparisonType) 
{ 
    if (value == null) 
    { 
     throw new ArgumentNullException("value"); 
    } 
    if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) 
    { 
     throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType"); 
    } 
    if (this == value) 
    { 
     return true; 
    } 
    if (value.Length == 0) 
    { 
     return true; 
    } 
    switch (comparisonType) 
    { 
    case StringComparison.CurrentCulture: 
     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 
    case StringComparison.CurrentCultureIgnoreCase: 
     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 
    case StringComparison.InvariantCulture: 
     return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 
    case StringComparison.InvariantCultureIgnoreCase: 
     return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 
    case StringComparison.Ordinal: 
     return this.Length >= value.Length && string.nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0; 
    case StringComparison.OrdinalIgnoreCase: 
     return this.Length >= value.Length && TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0; 
    default: 
     throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType"); 
    } 

相比之下,我看到唯一的區別就是額外的長度檢查。