2013-07-16 12 views
14

Betrachtet man die folgenden Testprogramme:Warum macht Dereferenzierung mein Programm schneller?

Loop value on the stack

int main(void) { 
    int iterations = 1000000000; 

    while (iterations > 0) 
     -- iterations; 
} 

Loop value on the stack (dereferenced)

int main(void) { 
    int iterations = 1000000000; 
    int * p = & iterations; 

    while (* p > 0) 
     -- * p; 
} 

Loop value on the heap

#include <stdlib.h> 

int main(void) { 
    int * p = malloc(sizeof(int)); 
    * p = 1000000000; 

    while (*p > 0) 
     -- * p; 
} 

von ihnen mit -O0 kompilieren, erhalte ich t er folgende Ausführungszeiten:

case1.c 
real 0m2.698s 
user 0m2.690s 
sys  0m0.003s 

case2.c 
real 0m2.574s 
user 0m2.567s 
sys  0m0.000s 

case3.c 
real 0m2.566s 
user 0m2.560s 
sys  0m0.000s 

[Bearbeiten] Im Folgenden ist der Durchschnitt auf 10 Ausführungen:

case1.c 
2.70364 

case2.c 
2.57091 

case3.c 
2.57000 

Warum ist die Ausführungszeit größer mit dem ersten Testfall, der die einfachste zu sein scheint?

Meine aktuelle Architektur ist eine x86 virtuelle Maschine (Archlinux). Ich erhalte diese Ergebnisse sowohl mit gcc (4.8.0) als auch mit clang (3.3).

[Bearbeiten 1] Generierte Assembler-Codes sind fast identisch, außer dass die zweite und die dritte Anweisung mehr Befehle haben als die erste.

[Bearbeiten 2] Diese Leistungen sind reproduzierbar (auf meinem System). Jede Ausführung hat die gleiche Größenordnung.

[Bearbeiten] Ich interessiere mich nicht wirklich für die Leistung eines nicht optimierten Programms, aber ich verstehe nicht, warum es langsamer wäre, und ich bin neugierig.

+9

Haben Sie versucht, den generierten Code zu sehen? Warum interessieren Sie sich überhaupt für die Leistung von nicht optimiertem Code? –

+8

Hae, du hast versucht, sie in einer anderen Reihenfolge zu führen? Sind diese Single-Shot-Zeiten oder Durchschnittswerte über eine beträchtliche Anzahl von Läufen? – EJP

+0

@CarlNorum Fast der gleiche generierte Code, nur dass es in den letzten beiden Beispielen mehr Anweisungen (move & load) gibt. Performances interessiert mich nicht, aber ich bin immer noch neugierig :) –

Antwort

6

Es ist schwer zu sagen, ob dies der Grund ist, da ich etwas rate und Sie nicht einige Details angegeben haben (wie welches Ziel Sie verwenden). Aber was ich sehe, wenn ich ohne optimziations mit einem x86-Ziel kompilieren sind folgende Sequenzen für decrementign die iterations Variable:

Fall 1:

L3: 
    sub DWORD PTR [esp+12], 1 
L2: 
    cmp DWORD PTR [esp+12], 0 
    jg L3 

Fall 2:

L3: 
    mov eax, DWORD PTR [esp+12] 
    mov eax, DWORD PTR [eax] 
    lea edx, [eax-1] 
    mov eax, DWORD PTR [esp+12] 
    mov DWORD PTR [eax], edx 
L2: 
    mov eax, DWORD PTR [esp+12] 
    mov eax, DWORD PTR [eax] 
    test eax, eax 
    jg L3 

Ein großer Unterschied dass Sie in Fall 1 sehen, dass die Anweisung L3 den Speicherort liest und schreibt. Es folgt unmittelbar eine Anweisung, die denselben Speicherplatz liest, der gerade geschrieben wurde. Diese Art von Anweisungsfolge (derselbe Speicherplatz, der dann direkt in der nächsten Anweisung verwendet wird) verursacht in modernen CPUs häufig eine Art Pipeline-Blockierung.

Sie beachten werden, dass der Schreib sofort von einem Lesevorgang der gleichen Stelle gefolgt ist nicht im Fall 2.

wieder - diese Antwort ein wenig informierten Spekulation.

+0

Haben Sie Ihre getestet, weil Ihr ASM-Code anders ist als OPs – aaronman

+0

OPs scheint die von Clang zu sein. Ich habe das gleiche mit gcc 4.8. – snf

+2

@snf die Wahrheit ist Zweifel, diese Frage wird eine interessante Antwort genug für jedermann mit – aaronman

Verwandte Themen