Der C # -Compiler selbst ändert die emittierte IL im Release-Build nicht wesentlich. Bemerkenswert ist, dass keine NOP-Opcodes mehr ausgegeben werden, mit denen Sie einen Haltepunkt für eine geschweifte Klammer festlegen können. Der große ist der Optimierer, der in den JIT-Compiler integriert ist. Ich weiß, dass es die folgenden Optimierungen vornimmt:
Methode Inlining. Ein Methodenaufruf wird durch das Einfügen des Codes der Methode ersetzt. Dies ist eine große Sache, es macht Immobilien-Accessoren im Wesentlichen kostenlos.
CPU-Registerzuordnung. Lokale Variablen und Methodenargumente können in einem CPU-Register gespeichert bleiben, ohne jemals (oder weniger häufig) im Stapelrahmen gespeichert zu werden. Dies ist eine große Sache, die das Debuggen von optimiertem Code so schwierig macht. Und dem flüchtigen Schlüsselwort eine Bedeutung geben.
Eliminierung der Array-Indexprüfung. Eine wichtige Optimierung bei der Arbeit mit Arrays (alle .NET-Auflistungsklassen verwenden intern ein Array). Wenn der JIT-Compiler überprüfen kann, dass eine Schleife ein Array niemals außerhalb der Grenzen indiziert, wird die Indexprüfung aufgehoben. Großes.
Schleife abrollen. Schleifen mit kleinen Körpern werden verbessert, indem der Code bis zu viermal im Körper wiederholt wird und weniger Schleifen ausgeführt werden. Reduziert die Verzweigungskosten und verbessert die superskalaren Ausführungsoptionen des Prozessors.
Eliminierung des toten Codes. Eine Aussage wie if (false) {/ ... /} wird vollständig eliminiert. Dies kann durch ständiges Falten und Inlining auftreten. In anderen Fällen kann der JIT-Compiler feststellen, dass der Code keine möglichen Nebenwirkungen hat. Diese Optimierung macht das Profilieren von Code so schwierig.
Code heben. Code innerhalb einer Schleife, der nicht von der Schleife betroffen ist, kann aus der Schleife verschoben werden. Der Optimierer eines C-Compilers wird viel mehr Zeit damit verbringen, Möglichkeiten zum Heben zu finden. Aufgrund der erforderlichen Datenflussanalyse ist dies jedoch eine teure Optimierung, und der Jitter kann sich die Zeit nicht leisten, sodass nur offensichtliche Fälle angehoben werden. Erzwingen, dass .NET-Programmierer besseren Quellcode schreiben und sich selbst hochziehen.
Gemeinsame Eliminierung von Subausdrücken. x = y + 4; z = y + 4; wird z = x; Ziemlich häufig in Anweisungen wie dest [ix + 1] = src [ix + 1]; Zur besseren Lesbarkeit geschrieben, ohne eine Hilfsvariable einzuführen. Die Lesbarkeit muss nicht beeinträchtigt werden.
Ständiges Falten. x = 1 + 2; wird x = 3; Dieses einfache Beispiel wird vom Compiler frühzeitig erkannt, tritt jedoch zur JIT-Zeit auf, wenn andere Optimierungen dies ermöglichen.
Weitergabe kopieren. x = a; y = x; wird y = a; Dies hilft dem Registerzuweiser, bessere Entscheidungen zu treffen. Es ist eine große Sache im x86-Jitter, weil es nur wenige Register gibt, mit denen man arbeiten kann. Die Auswahl der richtigen ist für die Perfektion von entscheidender Bedeutung.
Dies sind sehr wichtige Optimierungen, die einen großen Unterschied machen können, wenn Sie beispielsweise den Debug-Build Ihrer App profilieren und mit dem Release-Build vergleichen. Das ist jedoch nur dann wirklich wichtig, wenn sich der Code auf Ihrem kritischen Pfad befindet. Die 5 bis 10% des Codes, den Sie schreiben, wirken sich tatsächlich auf die Leistung Ihres Programms aus. Der JIT-Optimierer ist nicht intelligent genug, um im Voraus zu wissen, was wichtig ist. Er kann nur das Einstellrad "Drehen auf elf" für den gesamten Code anwenden.
Das effektive Ergebnis dieser Optimierungen für die Ausführungszeit Ihres Programms wird häufig durch Code beeinflusst, der an anderer Stelle ausgeführt wird. Lesen einer Datei, Ausführen einer Datenbankabfrage usw. Die Arbeit des JIT-Optimierers wird vollständig unsichtbar. Es macht aber nichts aus :)
Der JIT-Optimierer ist ein ziemlich zuverlässiger Code, vor allem, weil er millionenfach getestet wurde. Es ist äußerst selten, dass Probleme in der Release-Build-Version Ihres Programms auftreten. Es passiert jedoch. Sowohl der x64- als auch der x86-Jitter hatten Probleme mit Strukturen. Der x86-Jitter hat Probleme mit der Gleitkommakonsistenz und führt zu geringfügig unterschiedlichen Ergebnissen, wenn die Zwischenprodukte einer Gleitkommaberechnung mit einer Genauigkeit von 80 Bit in einem FPU-Register gespeichert werden, anstatt beim Löschen in den Speicher abgeschnitten zu werden.