24 Stimmen

Warum ist String.IsNullOrEmpty schneller als String.Length?

ILSpy zeigt, dass String.IsNullOrEmpty in Bezug auf String.Length implementiert ist. Aber warum ist dann String.IsNullOrEmpty(s) schneller als s.Length == 0?

Zum Beispiel ist es in diesem Benchmark 5% schneller:

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[] { 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();
}

(Andere Benchmarks zeigen ähnliche Ergebnisse. Dieser hier minimierte den Effekt von überflüssigen Prozessen auf meinem Computer. Außerdem kamen die Tests, die mit leeren Zeichenfolgen verglichen wurden, auf dasselbe Ergebnis, etwa 13% langsamer als IsNullOrEmpty.)

Zusätzlich, warum ist IsNullOrEmpty nur auf x86 schneller, während auf x64 String.Length etwa 9% schneller ist?

Update: Details zur Testeinrichtung: .NET 4.0 auf 64-Bit Windows 7, Intel Core i5 Prozessor, Konsolenprojekt, kompiliert mit aktivierter "Optimierung des Codes". Allerdings war auch "JIT-Optimierung beim Modulladen unterdrücken" aktiviert (siehe akzeptierte Antwort und Kommentare).

Bei vollständig aktivierter Optimierung ist Length etwa 14% schneller als IsNullOrEmpty mit dem Delegaten und anderem Overhead entfernt, wie in diesem Test:

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; // Längentest durch String.IsNullOrEmpty ersetzen

25voto

Ňuf Punkte 5936

Es liegt daran, dass Sie Ihren Benchmark innerhalb von Visual Studio ausgeführt haben, was den JIT-Compiler daran hindert, den Code zu optimieren. Ohne Optimierungen wird dieser Code für String.IsNullOrEmpty erzeugt.

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 

und vergleichen Sie ihn jetzt mit dem für Length == 0 erzeugten Code.

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 

Sie sehen, dass der Code für Length == 0 alles tut, was der Code für String.IsNullOrEmpty tut, aber zusätzlich versucht er, den booleschen Wert (der von der Längenvergleichsrückgabe stammt) noch einmal umständlich in einen booleschen Wert umzuwandeln, was ihn langsamer macht als String.IsNullOrEmpty.

Wenn Sie das Programm mit aktivierten Optimierungen kompilieren (Release-Modus) und die .exe-Datei direkt von Windows ausführen, ist der vom JIT-Compiler generierte Code viel besser. Für String.IsNullOrEmpty lautet er:

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

und für Length == 0:

001406f0   cmp     dword ptr [ecx+4],0
001406f4   sete    al
001406f7   movzx   eax,al
001406fa   ret

Mit diesem Code sind die Ergebnisse wie erwartet, d.h. Length == 0 ist etwas schneller als String.IsNullOrEmpty.

Es ist auch erwähnenswert, dass die Verwendung von Linq, Lambda-Ausdrücken und die Berechnung des Modulos in Ihrem Benchmark keine gute Idee ist, weil diese Operationen langsam sind (im Vergleich zur Zeichenfolgenvergleich) und das Ergebnis des Benchmarks ungenau machen.

4voto

Alexei Levenkov Punkte 96742

Ihr Benchmark misst nicht String.IsNullOrEmpty vs String.Length, sondern wie unterschiedliche Lambda-Ausdrücke zu Funktionen generiert werden. Es ist also nicht sehr überraschend, dass ein Delegat, der nur einen einzigen Funktionsaufruf enthält (IsNullOrEmpty), schneller ist als einer mit Funktionsaufruf und Vergleich (Length == 0).

Um einen direkten Vergleich der tatsächlichen Aufrufe zu erhalten - schreiben Sie Code, der sie direkt ohne Delegaten aufruft.

EDIT: Meine groben Messungen zeigen, dass die Version mit dem Delegaten IsNullOrEmpty etwas schneller ist als der Rest, während direkte Aufrufe des gleichen Vergleichs in umgekehrter Reihenfolge liegen (und etwa doppelt so schnell sind aufgrund einer signifikant geringeren Anzahl von zusätzlichem Code) auf meinem Computer. Die Ergebnisse können zwischen Maschinen, x86/x64-Modus und auch zwischen verschiedenen Runtime-Versionen variieren. Für praktische Zwecke würde ich alle 4 Methoden für etwa gleichwertig halten, wenn Sie sie in LINQ-Abfragen verwenden möchten.

Insgesamt bezweifle ich, dass es einen messbaren Unterschied im realen Programm gibt, der sich durch die Wahl zwischen diesen Methoden ergibt. Wählen Sie also diejenige, die für Sie am leserlichsten ist, und verwenden Sie sie. Ich ziehe im Allgemeinen IsNullOrEmpty vor, da dies die geringste Chance bietet, == oder != in einer Bedingung falsch zu verwenden.

Das Entfernen von Zeichenkettenmanipulationen insgesamt aus zeitkritischen Codes wird wahrscheinlich einen viel höheren Nutzen bringen als die Wahl zwischen diesen Optionen, auch das Verzichten auf LINQ für kritische Codes ist eine Möglichkeit. Wie immer - stellen Sie sicher, dass Sie die Gesamtgeschwindigkeit des Programms in einem realen Szenario messen.

1voto

Petr Abdulin Punkte 32034

Dein Test ist irgendwo falsch. IsNullOrEmpty kann per Definition nicht schneller sein, da es eine zusätzliche Nullvergleichsoperation durchführt und dann die Länge überprüft.

Also kann die Antwort sein: es ist schneller aufgrund deines Tests. Allerdings zeigt selbst dein Code, dass IsNullOrEmpty auf meinem Computer sowohl im x86- als auch im x64-Modus durchgehend langsamer ist.

1voto

DmitryG Punkte 17497

Ich glaube, dein Test ist nicht korrekt:

Dieser Test zeigt, dass string.IsNullOrEmpty immer langsamer ist als s.Length==0, weil er einen zusätzlichen Null-Check durchführt:

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[] { 
    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);

Ergebnisse:

6573
5328
5488
6419

Du kannst s.Length==0 verwenden, wenn du sicher bist, dass die Zieldaten keine Null-Strings enthalten. In anderen Fällen würde ich vorschlagen, String.IsNullOrEmpty zu verwenden.

0voto

Dummy01 Punkte 1955

Ich denke, es ist unmöglich, dass IsNullOrEmpty schneller ist, da es, wie alle anderen gesagt haben, auch eine Überprüfung auf Null durchführt. Aber schneller oder nicht, der Unterschied wird so gering sein, dass dies ein Pluspunkt für die Verwendung von IsNullOrEmpty ist, nur aufgrund dieser zusätzlichen Nullprüfung, die Ihren Code sicherer macht.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X