2016-08-16 2 views
0

Ich versuche, zu vergleichen Multiplikation Lesen in unserer Sammlung zu sehen, ob eine Verstärkung zwischen dem SchreibenMontage vs Division

gibt es
x/3.0 

gegen const double one_over_three = 1,0/3,0; x * one_over_three;

Aber ich brauche ein bisschen Hilfe lesen Assembly, die ich nicht gelernt habe.

I

zusammengestellt
int div3(double x) { 
    const double three = 1/3.0; 
    return x*three; 
} 

und erhalten

div3(double): 
    pushq %rbp 
    movq %rsp, %rbp 
    movsd %xmm0, -24(%rbp) 
    movabsq $4599676419421066581, %rax 
    movq %rax, -8(%rbp) 
    movsd -24(%rbp), %xmm1 
    movsd .LC0(%rip), %xmm0 
    mulsd %xmm1, %xmm0 
    cvttsd2si  %xmm0, %eax 
    popq %rbp 
    ret 
    .LC0: 
    .long 1431655765 
    .long 1070945621 

Dann

zusammengestellt
int div3(double x) { 
    return x/3.0; 
} 

und erhalten

div3(double): 
    pushq %rbp 
    movq %rsp, %rbp 
    movsd %xmm0, -8(%rbp) 
    movsd -8(%rbp), %xmm0 
    movsd .LC0(%rip), %xmm1 
    divsd %xmm1, %xmm0 
    cvttsd2si  %xmm0, %eax 
    popq %rbp 
    ret 
    .LC0: 
    .long 0 
    .long 1074266112 

Q1: Kann mir jemand freundlicherweise bei der Montage helfen?

Q2: Was bedeuten diese beiden letzten Zeilen in jedem der Codes?

+0

Ich würde sagen, dass der erste ASM-Code länger ist als der zweite, so dass es etwas länger dauern kann. – ForceBru

+1

Versuchen Sie Folgendes: https: //en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax –

+0

Sie müssen wirklich Zeit sie zu finden, und natürlich, dass das Ergebnis gilt nur für diese Maschine an der Adresse, wo Sie die Tests gefunden. Sie sollten auch versuchen, zu optimieren, wenn Sie kompilieren. Es kann einen Teil dieses Codes und einige der Zugriffe auf beide Tests reduzieren. –

Antwort

6

Wenn Sie so Optimierungen vornehmen, tun Sie es falsch. Es tut mir leid, ich verwende normalerweise keine negativen Begriffe, wenn ich etwas erkläre, aber du bist auf einem falschen Weg.

Was Sie tun, heißt vorzeitige Optimierung und Mikro-Optimierung. Sie versuchen, etwas zu optimieren, von dem Sie nicht wissen, dass es optimiert werden muss. Erstens, und das ist ein Deal Breaker: Verwenden Sie Compiler-Optimierungen. Und dann würden Sie normalerweise profilieren und versuchen, die Krisenherde zu optimieren.


Mal sehen, wie relevant die Optimierung Sie versuchen zu tun ist:

ich zunächst mit -O3 (gcc 6.1) kompilieren: Lassen Sie uns die Ausgabe (auch auf der Godbolt compiler explorer) sehen:

mul_inverse3(double): 
     mulsd .LC0(%rip), %xmm0 
     cvttsd2si  %xmm0, %eax 
     ret 

div3(double): 
     divsd .LC1(%rip), %xmm0 
     cvttsd2si  %xmm0, %eax 
     ret 

wo .LCO und .LC1 sind die Konstanten: der nächste double-1/3.0 und 3.0:

.LC0: 
     .long 1431655765 
     .long 1070945621 
.LC1: 
     .long 0 
     .long 1074266112 

Ok, so sehen Sie bereits, wie viel der Compiler für sich selbst tun kann. Dies ist, was Sie gesehen haben sollten, nicht die lauten -O0 Ausgabe.

Welcher ist schneller? Als Faustregel gilt: Multiplikation ist schneller als Division auf allen CPUs. Das Verhältnis hängt von der spezifischen Mikroarchitektur ab. Für x86 siehe Agner Fog's instruction tables and microarch pdf und andere Perf-Links im -Tag-Wiki. Zum Beispiel, ~ 3x die Latenz und ~ 1/12 der Durchsatz bei Intel Sandybridge. Und trotzdem ist div vielleicht nicht einmal der Flaschenhals, also kann div vs mul die Gesamtleistung nicht beeinflussen. Cache-Misses, andere Pipelines oder andere Dinge wie IO könnten den Unterschied komplett verbergen.


Aber sie sind immer noch anders. Können wir den gleichen Code vom Compiler erhalten? Ja! Fügen Sie -ffast-math hinzu. Mit dieser Option kann der Compiler Floating-Operationen neu anordnen/ändern, auch wenn sich das Ergebnis etwas ändert (was Sie von Hand versuchen möchten). Seien Sie jedoch vorsichtig, da dies für das gesamte Programm gilt.

mul_inverse3(double): 
     mulsd .LC0(%rip), %xmm0 
     cvttsd2si  %xmm0, %eax 
     ret 

div3(double): 
     mulsd .LC0(%rip), %xmm0 
     cvttsd2si  %xmm0, %eax 
     ret 

Können wir mehr tun? Ja: Fügen Sie -march=native hinzu und suchen Sie in der Compiler-Dokumentation nach weiteren Optionen.

Das war also die erste Lektion: Lassen Sie den Compiler tun, es ist Optimierungen zuerst!


Hier kommt die zweite:

Sie verbringen 1 Woche versucht, eine zufällige Operationen zu optimieren. Du machst es endlich! Nach einer Woche harter Arbeit und schlaflosen Nächten machen Sie diese Operation 10-mal schneller (wow! Herzlichen Glückwunsch!). Dann starten Sie Ihr Programm und sehen, dass das Programm nur 1% schneller ist. Der Horror! Wie kann das möglich sein? Nun, wenn Ihr Programm nur 1% seiner Zeit damit verbringt, diese Operation auszuführen, dann ... Sie bekommen den Punkt. Oder es können andere Mechanismen darunter wie OS-Optimierungen, CPU-Pipeline usw. vorhanden sein, die eine Operation 100 Mal wiederholen, um viel weniger als 100 Mal mehr zu verbrauchen. Was Sie tun müssen, ist Profil zuerst! Finden Sie die heißen Schleifen und optimieren Sie diese! Optimiere die Schleife/Funktion, die das Programm 60% seiner Zeit verbringt.

Noch wichtiger ist, suchen Sie nach High-Level-Optimierungen, damit Ihr Code weniger Arbeit leisten kann, anstatt nur die gleiche Arbeit schneller zu erledigen.

+0

hah, du schlägst mich um ein paar Sekunden ... –

+0

@dwelch schnell sein oder tot sein! :) – bolov

+1

-ffast-math ist gefährlich und gilt für alles auf einmal. Ersetzen einer bestimmten Division (wo Sie wissen, dass es in Ordnung ist) durch Multiplikation mit reziprok ist vollständig gültig und nicht Mikro. – harold

3

Die .longs sind einfach die Konstanten, die Sie in den Funktionen 1/3 und 3. 3.0 in Doppel liefern, ist 0x4008000000000000, wenn Sie in 32-Bit-Chunks zerlegt bekommen Sie eine von ihnen Null die andere 0x40080000, die in Dezimal ist 1074266112. Alle sie tun, gibt es Pre-Computing die Konstanten und laden sie in Registern, um die Mathematik durch den Code angefordert. Warum verwenden .longs und dezimal? Nun, sie müssen etwas auswählen, um die Bitmuster zu erzeugen, die sie haben wollten, es könnte ein Array von Bytes oder was auch immer gewesen sein. Und dezimal vs hex, die meisten Leute sogar Programmierer denken in Dezimal, so dass sie eins auswählen.

Das Gleiche gilt für die andere Konstante 1/3, die etwa 0x3FD5555555555555 ist die 1431655765 1070945621. Zugegeben ist float wird Ihnen einige Rundungsdifferenzen sehen könnte, wenn Sie diese berechnen sich

Wenn Sie optimieren Sie die Menge an asm reduzieren Sie haben, um herauszufinden

int div3(double x) { 
    return x/3.0; 
} 

und natürlich ändert sich dramatisch Ihre Leistung

0000000000000000 <div3>: 
    0: f2 0f 5e 05 00 00 00 divsd 0x0(%rip),%xmm0  # 8 <div3+0x8> 
    7: 00 
    8: f2 0f 2c c0    cvttsd2si %xmm0,%eax 
    c: c3      retq 

Disassembly of section .rodata.cst8: 

0000000000000000 <.LC0>: 
    0: 00 00     add %al,(%rax) 
    2: 00 00     add %al,(%rax) 
    4: 00 00     add %al,(%rax) 
    6: 08      .byte 0x8 
    7: 40      rex 

dieses ist nicht verknüpft. Beachten Sie, dass unsere Konstante in einer anderen Form ist.

int div3(double x) { 
    const double three = 1/3.0; 
    return x*three; 
} 

0000000000000000 <div3>: 
    0: f2 0f 59 05 00 00 00 mulsd 0x0(%rip),%xmm0  # 8 <div3+0x8> 
    7: 00 
    8: f2 0f 2c c0    cvttsd2si %xmm0,%eax 
    c: c3      retq 

Disassembly of section .rodata.cst8: 

0000000000000000 <.LC0>: 
    0: 55      push %rbp 
    1: 55      push %rbp 
    2: 55      push %rbp 
    3: 55      push %rbp 
    4: 55      push %rbp 
    5: 55      push %rbp 
    6: d5      (bad) 
    7: 3f      (bad) 

Und ich nehme an, Sie verstehen zu Punkt schweben, auch mit Doppel, gibt es keinen Grund, das gleiche Ergebnis von diesen beiden Funktionen zu erwarten.

+0

der ganze Code macht die Multiplikation oder Division gegen eine vorberechnete Konstante und die Umwandlung in int. –