Подтвердить что ты не робот

Почему 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
4b9b3361

Ответ 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 (я думаю).

Мир!

  • : редактировать