2016-04-05 19 views
5

Ich arbeite an Optimierungsverfahren, die vom .Net Native Compiler durchgeführt werden. Ich habe einen Sample-Loops:Warum kompiliert .Net Native die Schleife in umgekehrter Reihenfolge?

 for (int i = 0; i < 100; i++) 
     { 
      Function(); 
     } 

Und ich habe es mit india zusammengestellt. Dann zerlegte ich das Ergebnis .dll Datei mit Maschinencode in IDA. Als Ergebnis habe ich:

enter image description here

(ich ein paar unnötige Linien entfernt haben, also keine Sorge, dass Adresse Linien inconsistant sind)

Ich verstehe, dass add esi, 0FFFFFFFFh wirklich bedeutet subtract one from esi and alter Zero Flag if needed, so Wir können zum Anfang springen, wenn die Null noch nicht erreicht ist.

Was ich nicht verstehe ist, warum hat der Compiler die Schleife reaverse?

Ich kam zu dem Schluss, dass

LOOP: 
add esi, 0FFFFFFFFh 
jnz LOOP 

als zum Beispiel nur schneller ist

LOOP: 
inc esi 
cmp esi, 064h 
jl LOOP 

Aber ist es aus diesem Grunde wirklich und ist der Geschwindigkeitsunterschied wirklich wichtig?

+0

ADD mit einem sofortigen Wert ist schneller als INC und Sie überspringen auch CMP ... alle diese in 3 Zeilen Code. Dann ja, Unterschied ist wirklich wichtig (sowohl in Größe und Geschwindigkeit). Stellen Sie sich vor, Sie tun dies in ~ 30000 Orten in einem realen Weltprogramm ... –

+0

Ja, es ist schneller, und im Allgemeinen werden Optimierer jede mögliche Optimierung anwenden, die Ihren Code schneller macht, ohne die Semantik Ihres Programms zu ändern. –

+0

In Bezug auf die umgekehrte Richtung ist vielleicht der Vergleich mit Null schneller als der Vergleich mit einem bestimmten Wert? – user5226582

Antwort

3

inc might be slower than add because of the partial flag update. Darüber hinaus wirkt sich add auf das Null-Flag aus, so dass Sie keine weitere cmp-Anweisung verwenden müssen. Springe einfach direkt.

Dies ist eine bekannte Art von loop optimization

Umkehrung: Loop Umkehr kehrt die Reihenfolge, in der Werte der Index-Variablen zugewiesen sind. Dies ist eine subtile Optimierung, die dazu beitragen kann, Abhängigkeiten zu beseitigen und somit andere Optimierungen zu ermöglichen. Bestimmte Architekturen verwenden auch Schleifenkonstrukte auf Assembly-Sprachebene, die nur in einer einzigen Richtung zählen (z. B. Dekrement-Sprung-wenn-nicht-Null (DJNZ)).

Sie das Ergebnis here für andere Compiler sehen können.

+0

'inc' ist langsamer als' add' um einen Taktzyklus. Vergleichen Sie sie im [Intel® 64 und IA-32 Architectures Optimization Reference Manual] (http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures- optimization-manual.html) Scrollen Sie nach unten zu Anhang C und Sie sehen die Latenz und die Durchlaufzeit jedes x86/x6 4 Anweisungen. Ein Taktzyklus mag nicht signifikant erscheinen, aber wenn Sie Hunderte oder Tausende von Schleifen haben, wird sich das schnell summieren. – Icemanind

1

Ihre Schlussfolgerung richtig ist: invertiert Zyklus zielt 0 (Zyklus endet, wenn Registerwert 0 erreichen), so dass Add Null-Flag im bedingten Verzweigungs verwendet gesetzt.

Auf diese Weise brauchen Sie nicht dedizierten Cmp, was zu: 1) Größenoptimierung 2) es ist auch schneller (Abschluss von Compiler Programmierer Entscheidung und andere answer).

Das ist ziemlich häufig Assembler-Trick zu schreiben Loop-Targeting 0. Ich bin überrascht, dass Sie Assembler verstehen, aber nicht wissen (fragen).