Eine Probenmessung von meiner Maschine, dem Test 10 Mal ausgeführt wird, PowerNoLoop
ist zuerst:
00:00:00.0277138 00:00:00.0001553
00:00:00.0000142 00:00:00.0000057
00:00:00.0000106 00:00:00.0000053
00:00:00.0000084 00:00:00.0000053
00:00:00.0000080 00:00:00.0000053
00:00:00.0000075 00:00:00.0000053
00:00:00.0000080 00:00:00.0000057
00:00:00.0000080 00:00:00.0000053
00:00:00.0000080 00:00:00.0000053
00:00:00.0000075 00:00:00.0000053
Ja, etwa 50% langsamer. Bemerkenswert ist der Jitter-Overhead im ersten Durchgang durch den Test, offensichtlich brennt es viel mehr Kern, der versucht, diese riesige Methode zu kompilieren. Beachten Sie, dass die Messung sehr unterschiedlich ist, wenn Sie den Optimierer nicht deaktivieren, die No-Loop-Version ist dann ~ 800% langsamer.
Der erste Ort, um immer nach einer Erklärung zu suchen, ist der generierte Maschinencode, den Sie mit Debug> Windows> Disassembly sehen können. Der primäre Problempunkt ist der Prolog der PowerNoLoop()
Methode. Sieht aus wie dies in x86-Code:
067E0048 push ebp ; setup stack frame
067E0049 mov ebp,esp
067E004B push edi ; preserve registers
067E004C push esi
067E004D sub esp,0FA8h ; stack frame size = 4008 bytes
067E0053 mov esi,ecx
067E0055 lea edi,[ebp-0ACCh] ; temp2 variables
067E005B mov ecx,2B1h ; initialize 2756 bytes
067E0060 xor eax,eax ; set them to 0
067E0062 rep stos dword ptr es:[edi]
Beachten Sie die sehr große Stapelgröße, 4008 Bytes. Viel zu viel für eine Methode mit nur einer lokalen Variablen, sie sollte nur 8 Bytes benötigen. Die zusätzlichen 4000 von ihnen sind temporäre Variablen, ich nannte sie temp2
. Sie werden durch die Anweisung rep stos
auf 0 initialisiert, das dauert eine Weile. Ich kann 2756 nicht erklären.
Die einzelnen addieren sind eine sehr streichelnde Angelegenheit im nicht optimierten Code.Ich werde Ihnen den Dump Maschinencode schonen und es in äquivalenten C# -Code schreiben:
if (i >= arr.Length) goto throwOutOfBoundsException
var temp1 = arr[i];
if (i >= arr.Length) goto throwOutOfBoundsException
var temp2 = temp1 + arr[i];
if (i >= arr.Length) goto throwOutOfBoundsException
arr[i] = temp2
wiederholte immer und immer wieder, tausendmal insgesamt. Die temp2
Variable ist der Unruhestifter, es gibt jeweils einen für jede einzelne Anweisung. Dadurch werden der Stack-Frame-Größe 4000 Byte hinzugefügt. Wenn jemand 2756 erraten hat, würde ich es gerne in einem Kommentar hören.
Wenn Sie alle auf 0 setzen, bevor die Methode gestartet werden kann, ist das ungefähr die Ursache für die Verlangsamung um 50%. Es gibt wahrscheinlich auch einige Instruktionsabruf- und Decodierungs-Overhead, es kann nicht leicht von der Messung isoliert werden.
Bemerkenswert ist auch, dass sie nicht beseitigt werden, wenn Sie das [MethodImpl] -Attribut entfernen und dem Optimierer erlauben, seine Arbeit zu erledigen. Die Methode ist in der Tat nicht optimiert, sicherlich, weil sie nicht so einen großen Teil des Codes angehen will.
Schlussfolgerung, die Sie zeichnen sollten, ist es immer zu überlassen, den Jitter-Optimierer Loops für Sie zu entrollen. Es weiß es besser.
Wie haben Sie gemessen? Haben Sie Release-Builds verwendet? –
Erprobt sowohl Debug und Release. Getestet mit StopWatch über 200000 Arrays von 1000 Items –
Der zweite wird wahrscheinlich nicht von CPU-Befehl-Cache profitieren? – MickyD