2010-06-25 32 views
29

Một nhà tư vấn đến ngày hôm qua và bằng cách nào đó chủ đề của chuỗi đã xuất hiện. Ông đã đề cập rằng ông đã nhận thấy rằng đối với chuỗi ít hơn một độ dài nhất định, Contains thực sự nhanh hơn StartsWith. Tôi đã phải nhìn thấy nó với hai mắt của riêng tôi, vì vậy tôi đã viết một ứng dụng nhỏ và chắc chắn đủ, Contains là nhanh hơn!Chứa nhanh hơn StartsWith?

Làm cách nào có thể?

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

Đầu ra:

726ms using Contains 
865ms using StartsWith 

Tôi đã thử nó với chuỗi dài quá!

+2

Hai điều. Hãy thử chuyển đổi thứ tự để xem nó có ảnh hưởng đến kết quả không. Sau đó, vì đây là một câu hỏi thực hiện cụ thể, hãy xem mã nguồn, thông qua Reflector nếu cần. Có khả năng 'Contains' được tối ưu hóa cẩn thận hơn (có thể sử dụng mã nguồn gốc) vì nó được sử dụng thường xuyên hơn. –

+5

Tối ưu hóa vi mô hiếm khi hữu ích.Bạn đang so sánh một chuỗi có độ dài tối đa có thể là 20 ký tự hoặc hơn 10 triệu lần lặp và tiết kiệm được một con số khổng lồ ~ 140 mili giây. Hãy thử với chuỗi dài hơn hoặc trường hợp sử dụng hợp lệ hơn và xem bạn có nhận được cùng số hay không. – Chris

+10

Số đo thời gian của bạn bị thiếu sót. Bạn nên sử dụng đối tượng Đồng hồ bấm giờ để theo dõi thời gian, chứ không phải Ngày giờ. Nếu bạn định sử dụng DateTimes, ít nhất bạn nên sử dụng end.Subtract (start) .TotalMilliseconds –

Trả lời

25

Thử sử dụng StopWatch để đo tốc độ thay vì kiểm tra DateTime.

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

Tôi nghĩ rằng quan trọng là các phần sau đây quan trọng được in đậm:

Contains:

Phương pháp này thực hiện một văn hoá, thứ (case-sensitive và không nhạy cảm) so sánh.

StartsWith:

Phương pháp này thực hiện một từ (case-sensitive và văn hóa nhạy cảm) so sánh bằng cách sử dụng văn hóa hiện hành.

Tôi nghĩ rằng quan trọng là thứ tự so sánh mà số tiền:

Một loại thứ tự so sánh chuỗi dựa trên giá trị số của mỗi Char đối tượng trong chuỗi. Một so sánh thứ tự tự động là phân biệt chữ hoa chữ thường vì chữ thường và các phiên bản chữ hoa của một ký tự có các điểm mã khác nhau. Tuy nhiên, nếu trường hợp không quan trọng trong ứng dụng của bạn, bạn có thể chỉ định một so sánh thứ tự bỏ qua trường hợp. Điều này tương đương với việc chuyển đổi chuỗi thành chữ hoa bằng cách sử dụng văn bản bất biến và sau đó thực hiện so sánh thứ tự trên kết quả.

Tài liệu tham khảo:

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

Sử dụng Reflector bạn sẽ nhìn thấy mã cho hai:

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

Có! Chính xác. Như Daniel đã chỉ ra trong một bình luận khác, chuyển StringComparison.Ordinal thành StartsWith sẽ làm cho StartsWith nhanh hơn nhiều so với Contains. Tôi chỉ cần thử nó và có được "748.3209ms sử dụng Chứa 154.548ms bằng cách sử dụng StartsWith" – StriplingWarrior

+0

@StriplingWarrior, Đồng hồ bấm giờ không đáng tin cậy với quy trình ngắn. Sẽ luôn có các biến thể với mỗi bài kiểm tra. Bắt 748 vs 154 ... không đủ bằng chứng! Vì vậy, câu hỏi là, bao nhiêu lần bạn đã thử nghiệm quá trình ngắn của bạn ?? – usefulBee

+1

@usefulBee: Mã của câu hỏi ban đầu lặp lại phương thức gọi mười triệu lần, điều này đưa chúng ta vào hàng trăm mili giây. Điều đó thường đủ để làm mịn các biến thể khi không có I/O liên quan. [Đây là một kịch bản LINQPad] (http://share.linqpad.net/k7n66x.linq) cho thấy kết quả tương tự trong một chiếc giường thử nghiệm chuẩn xác hơn. – StriplingWarrior

22

Tôi đã tìm ra. Đó là vì StartsWith nhạy cảm với văn hóa, trong khi Contains thì không. Điều đó vốn có nghĩa là StartsWith phải làm nhiều việc hơn.

FWIW, đây là kết quả của tôi trên Mono với điểm chuẩn (điều chỉnh) dưới đây:

1988.7906ms using Contains 
10174.1019ms using StartsWith 

tôi sẽ rất vui mừng khi nhìn thấy kết quả của người dân về MS, nhưng điểm chính của tôi là thực hiện một cách chính xác (và giả định tối ưu hóa tương tự), tôi nghĩ StartsWith phải là chậm:

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

Thực sự đoán tốt, nhưng có khả năng không. Anh ta không đi qua trong văn hóa, và dòng này là trong việc thực hiện StartsWith: 'CultureInfo info = (culture == null)? CultureInfo.CurrentCulture: culture: ' –

+2

@Marc Bollinger - Tất cả những gì bạn thấy là StartsWith nhạy cảm với văn hóa, đó là yêu cầu bồi thường. – Lee

+0

@Marc, phải. Nó đang sử dụng văn hóa hiện tại. Đó là văn hóa nhạy cảm, và một số nền văn hóa dựa trên các quy tắc bình thường khá phức tạp. –

2

tôi twiddled xung quanh trong Reflector và tìm thấy một câu trả lời tiềm năng:

Chứa:

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

Và có một số quá tải để các nền văn hóa mặc định là CurrentCulture.

Vì vậy, trước hết, Ordinal sẽ nhanh hơn (nếu chuỗi gần đầu), đúng không? Và thứ hai, có nhiều logic ở đây có thể làm chậm mọi thứ xuống (mặc dù quá tầm thường)

+1

Tôi không đồng ý rằng 'CultureInfo.CurrentCulture.CompareInfo.IsPrefix' là tầm thường. –

+0

+1 - Tôi đã không thực sự đọc nó thành thật mà nói, tôi chỉ đề cập đến số tiền tuyệt đối của mã;) – hackerhasid

9

StartsWithContains hoạt động hoàn toàn khác khi nói đến các vấn đề nhạy cảm về văn hóa.

Cụ thể, StartsWith trả lại true KHÔNG bao hàm Contains trả lại true. Bạn nên thay thế một trong số họ với người kia chỉ khi bạn thực sự biết những gì bạn đang làm.

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

Hãy kiểm tra những gì ILSpy nói về hai ...

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

Hình như nó coi văn hóa là tốt, nhưng được mặc định.

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

Ngược lại, sự khác biệt duy nhất tôi thấy xuất hiện có liên quan là kiểm tra chiều dài thêm.