Почему String.IsNullOrEmpty быстрее, чем String.Length?
ILSpy показывает, что String.IsNullOrEmpty
реализуется в терминах String.Length
. Но тогда почему String.IsNullOrEmpty(s)
быстрее, чем s.Length == 0
?
Например, он на 5% быстрее в этом тесте:
var stopwatches = Enumerable.Range(0, 4).Select(_ => new Stopwatch()).ToArray();
var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] { s => s == String.Empty, s => s.Length == 0, s => String.IsNullOrEmpty(s), s => s == "" };
int count = 0;
for (int i = 0; i < 10000; ++i) {
stopwatches[i % 4].Start();
for (int j = 0; j < 1000; ++j)
count += strings.Count(testers[i % 4]);
stopwatches[i % 4].Stop();
}
(Другие тесты показывают аналогичные результаты, что минимизировало эффект крутильной работы на моем компьютере. Кроме того, в отличие от тестов, сравниваемых с пустыми строками, это было примерно на 13% медленнее, чем IsNullOrEmpty
.)
Кроме того, почему IsNullOrEmpty
работает только быстрее на x86, тогда как на x64 String.Length
примерно на 9% быстрее?
Обновление: Сведения о настройке тестирования:.NET 4.0, работающий на 64-битном Windows 7, процессор Intel Core i5, консольный проект, скомпилированный с включенным "Оптимизировать код". Тем не менее, "Запретить оптимизацию JIT при загрузке модуля" также было включено (см. Принятый ответ и комментарии).
При полностью включенной оптимизации Length
примерно на 14% быстрее, чем IsNullOrEmpty
, когда делегат и другие служебные данные удалены, как в этом тесте:
var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,,STU,V,,W,,X,,,Y,,Z,".Split(',');
int count = 0;
for (uint i = 0; i < 100000000; ++i)
count += strings[i % 32].Length == 0 ? 1 : 0; // Replace Length test with String.IsNullOrEmpty
Ответы
Ответ 1
Это потому, что вы запускали свой тест из Visual Studio, который не позволяет компилятору JIT оптимизировать код. Без оптимизации этот код создается для String.IsNullOrEmpty
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,8
00000006 mov dword ptr [ebp-8],ecx
00000009 cmp dword ptr ds:[00153144h],0
00000010 je 00000017
00000012 call 64D85BDF
00000017 mov ecx,dword ptr [ebp-8]
0000001a call 63EF7C0C
0000001f mov dword ptr [ebp-4],eax
00000022 movzx eax,byte ptr [ebp-4]
00000026 mov esp,ebp
00000028 pop ebp
00000029 ret
а теперь сравните его с кодом, созданным для Length == 0
00000000 push ebp
00000001 mov ebp,esp
00000003 sub esp,8
00000006 mov dword ptr [ebp-8],ecx
00000009 cmp dword ptr ds:[001E3144h],0
00000010 je 00000017
00000012 call 64C95BDF
00000017 mov ecx,dword ptr [ebp-8]
0000001a cmp dword ptr [ecx],ecx
0000001c call 64EAA65B
00000021 mov dword ptr [ebp-4],eax
00000024 cmp dword ptr [ebp-4],0
00000028 sete al
0000002b movzx eax,al
0000002e mov esp,ebp
00000030 pop ebp
00000031 ret
Вы можете видеть, что этот код для Length == 0 делает все, что делает код для String.IsNullOrEmpty, но дополнительно пытается что-то вроде глупо конвертировать логическое значение (возвращенное из сравнения длины) снова в boolean, и это делает его медленнее, чем String.IsNullOrEmpty.
Если вы скомпилируете программу с включенными оптимизациями (режим выпуска) и запустите файл .exe непосредственно из Windows, код, сгенерированный компилятором JIT, намного лучше. Для String.IsNullOrEmpty это:
001f0650 push ebp
001f0651 mov ebp,esp
001f0653 test ecx,ecx
001f0655 je 001f0663
001f0657 cmp dword ptr [ecx+4],0
001f065b sete al
001f065e movzx eax,al
001f0661 jmp 001f0668
001f0663 mov eax,1
001f0668 and eax,0FFh
001f066d pop ebp
001f066e ret
и для Length == 0:
001406f0 cmp dword ptr [ecx+4],0
001406f4 sete al
001406f7 movzx eax,al
001406fa ret
С этим кодом результат будет таким, как ожидалось, т.е. длина == 0 немного быстрее, чем String.IsNullOrEmpty.
Также стоит упомянуть, что использование Linq, лямбда-выражений и вычислений по модулю в вашем тесте не является такой хорошей идеей, потому что эти операции медленны (относительно сравнения строк) и делают результат теста неточным.
Ответ 2
В вашем тесте не измеряется String.IsNullOrEmpty vs String.Length, а скорее, как различные функции лямбда-выражения генерируются для функций. То есть неудивительно, что делегат, который просто содержит вызов одной функции (IsNullOrEmpty), быстрее, чем один с вызовом функции и сравнением (Length == 0).
Чтобы получить сравнение кода вызова вызова вызова, который вызывает их непосредственно без делегатов.
EDIT: мои грубые измерения показывают, что версия делегата с IsNullOrEmpty немного быстрее, чем остальные, тогда как прямые вызовы того же сравнения в обратном порядке (и примерно в два раза быстрее из-за значительно меньшего количества дополнительного кода) на моей машине. Результаты могут быть опасны между машинами, режим x86/x64, а также между версиями среды выполнения. Для практических целей я бы подумал, что все 4 способа примерно одинаковы, если вам нужно использовать их в запросах LINQ.
В целом я сомневаюсь, что в реальной программе будет измеряться разница между этими методами, поэтому выберите тот, который наиболее читабелен для вас и используйте его. Обычно я предпочитаю IsNullOrEmpty, поскольку он дает меньше шансов получить ==/!= Неправильный в состоянии.
Удаление строковых манипуляций в целом из критического временного кода будет иметь гораздо более высокую выгоду, поскольку выбор между этими вариантами, а также сброс LINQ для критического кода - это вариант. Как всегда - обязательно измерьте общую скорость программы в реальном сценарии.
Ответ 3
Тебе что-то не так. IsNullOrEmpty не может быть быстрее по определению, поскольку он выполняет дополнительную операцию сравнения с нулевым значением, а затем проверяет длину.
Таким образом, ответ может быть: он быстрее из-за вашего теста. Однако даже ваш код показывает, что IsNullOrEmpty последовательно медленнее на моей машине в режимах x86 и x64.
Ответ 4
Я считаю, что ваш тест неверен:
Этот тест показывает, что string.IsNullOrEmpty
всегда медленнее, чем s.Length==0
, потому что он выполняет дополнительную проверку нуля:
var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] {
s => s == String.Empty,
s => s.Length == 0,
s => String.IsNullOrEmpty(s),
s => s == "" ,
};
int n = testers.Length;
var stopwatches = Enumerable.Range(0, testers.Length).Select(_ => new Stopwatch()).ToArray();
int count = 0;
for(int i = 0; i < n; ++i) { // iterate testers one by one
Stopwatch sw = stopwatches[i];
var tester = testers[i];
sw.Start();
for(int j = 0; j < 10000000; ++j) // increase this count for better precision
count += strings.Count(tester);
sw.Stop();
}
for(int i = 0; i < testers.Length; i++)
Console.WriteLine(stopwatches[i].ElapsedMilliseconds);
Результаты:
6573
5328
5488
6419
Вы можете использовать s.Length==0
, когда вы убедитесь, что целевые данные не содержат нулевые строки. В других случаях я предлагаю вам использовать string.IsNullOrEmpty
.
Ответ 5
Я думаю, что невозможно IsNullOrEmpty
быть быстрее, потому что, как все остальные сказали, он также делает проверку на null. Но быстрее или не так, разница будет настолько малой, что это дает плюс при использовании IsNullOrEmpty
только из-за этой дополнительной проверки нулевого кода, которая делает ваш код более безопасным.
Ответ 6
В CLR через CSharp глава 10 "Свойства" Джефф Рихтер пишет:
Метод свойств может занять много времени; доступ к полям всегда завершается немедленно. Общей причиной использования свойств является выполнение синхронизации потоков, которая может остановить поток навсегда, и поэтому свойство не должно использоваться, если требуется синхронизация потоков. В этой ситуации предпочтительным является метод. Кроме того, если к вашему классу можно получить доступ удаленно (например, ваш класс получен из System.MarshalByRefObject
), вызов метода свойства будет очень медленным, и поэтому метод предпочтительнее свойства. На мой взгляд, классы, полученные из MarshalByRefObject
, никогда не должны использовать свойства.
Итак, если мы видим, что String.Length
- это свойство, а String.IsNullOrEmpty
- метод, который может выполняться быстрее, чем свойство String.Length
.
Ответ 7
это может быть вызвано типами задействованных переменных.
* Пустой, кажется, использует логическое значение, длина int (я думаю).
Мир!