Содержит быстрее, чем StartsWith?
Вчера пришел консультант, и как-то возник вопрос о струнах. Он упомянул, что он заметил, что для строк менее определенной длины Contains
на самом деле быстрее, чем StartsWith
. Мне приходилось видеть это своими глазами, поэтому я написал небольшое приложение и, конечно, Contains
быстрее!
Как это возможно?
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
Я тоже пробовал его с длинными строками!
Ответы
Ответ 1
Попробуйте использовать StopWatch
для измерения скорости вместо проверки DateTime
.
Секундомер против использования System.DateTime.Now для событий синхронизации
Я думаю, что ключ следующий: важные части выделены жирным шрифтом:
Contains
:
Этот метод выполняет порядковый номер(с учетом регистра и нечувствительность к культуре).
StartsWith
:
Этот метод выполняет слово(чувствительный к регистру и чувствительный к культуре) сравнение с использованием текущей культуры.
Я думаю, что ключ является порядковым сравнением, которое равно:
Сортировка по порядку сравнивает строки на числовое значение каждого Charобъект в строке. Порядковый сравнение автоматически чувствителен к регистру, потому что нижний регистр и в верхнем регистре символов имеют разные кодовые точки. Однако, если дело не важно в вашем приложения, вы можете указать порядковое сравнение, которое игнорирует случай. Это эквивалентно преобразованию строка в верхний регистр, используя инвариантной культуры, а затем порядковое сравнение результата.
Литература:
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
Используя Reflector, вы можете увидеть код для двух:
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);
}
Ответ 2
Я понял это. Это потому, что StartsWith
чувствителен к культуре, а Содержит не является. Это по своей сути означает, что 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);
}
}
Ответ 3
StartsWith
и Contains
ведут себя совершенно иначе, когда речь идет о проблемах, связанных с культурой.
В частности, 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
}
}
Ответ 4
Я крутился вокруг в Reflector и нашел потенциальный ответ:
Содержит:
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.
Итак, в первую очередь, Ординал будет быстрее (если строка близка к началу), так или иначе? И, во-вторых, здесь существует больше логики, которая могла бы замедлить работу (хотя это так тривиально)
Ответ 5
Давайте рассмотрим, что говорит 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");
}
В отличие от этого, единственное различие, которое я вижу в этом вопросе, является дополнительной проверкой длины.