2015-11-05 3 views
11

Ich experimentiere mit der generierten Assembly und fand eine interessante Sache. Es gibt zwei Funktionen, die eine identische Berechnung durchführen. Der einzige Unterschied zwischen ihnen ist die Art und Weise, wie die Ergebnisse zusammengefasst werden.Harte Unterschiede in der generierten Assembly von Gleitkomma-Vergleichen < and > =

#include <cmath> 

double func1(double x, double y) 
{ 
    double result1; 
    double result2; 

    if (x*x < 0.0) result1 = 0.0; 
    else 
    { 
    result1 = x*x+x+y; 
    } 

    if (y*y < 0.0) result2 = 0.0; 
    else 
    { 
    result2 = y*y+y+x; 
    } 

    return (result1 + result2) * 40.0; 
} 

double func2(double x, double y) 
{ 
    double result = 0.0; 

    if (x*x >= 0.0) 
    { 
    result += x*x+x+y; 
    } 

    if (y*y >= 0.0) 
    { 
    result += y*y+y+x; 
    } 

    return result * 40.0; 
} 

Die Montage von x86 Klirren 3.7 mit -O2 Schalter auf gcc.godbolt.org erzeugt ist noch so viel anders und unerwartet. (Kompilierung auf gcc Ergebnisse in ähnlicher Montage)

.LCPI0_0: 
    .quad 4630826316843712512  # double 40 
func1(double, double):        # @func1(double, double) 
    movapd %xmm0, %xmm2 
    mulsd %xmm2, %xmm2 
    addsd %xmm0, %xmm2 
    addsd %xmm1, %xmm2 
    movapd %xmm1, %xmm3 
    mulsd %xmm3, %xmm3 
    addsd %xmm1, %xmm3 
    addsd %xmm0, %xmm3 
    addsd %xmm3, %xmm2 
    mulsd .LCPI0_0(%rip), %xmm2 
    movapd %xmm2, %xmm0 
    retq 

.LCPI1_0: 
    .quad 4630826316843712512  # double 40 
func2(double, double):        # @func2(double, double) 
    movapd %xmm0, %xmm2 
    movapd %xmm2, %xmm4 
    mulsd %xmm4, %xmm4 
    xorps %xmm3, %xmm3 
    ucomisd %xmm3, %xmm4 
    xorpd %xmm0, %xmm0 
    jb .LBB1_2 
    addsd %xmm2, %xmm4 
    addsd %xmm1, %xmm4 
    xorpd %xmm0, %xmm0 
    addsd %xmm4, %xmm0 
.LBB1_2: 
    movapd %xmm1, %xmm4 
    mulsd %xmm4, %xmm4 
    ucomisd %xmm3, %xmm4 
    jb .LBB1_4 
    addsd %xmm1, %xmm4 
    addsd %xmm2, %xmm4 
    addsd %xmm4, %xmm0 
.LBB1_4: 
    mulsd .LCPI1_0(%rip), %xmm0 
    retq 

func1 kompiliert zu einer branchless Versammlung, an denen viel weniger Anweisungen als func2. somit wird erwartet, dass func2 viel langsamer als func1 ist.

Kann jemand dieses Verhalten erklären?

+6

Der offensichtlichste Unterschied ist, dass der Optimierer weiß, dass 'x * x <0.0 'auf' false 'optimiert werden kann, aber nicht' x * x> = 0.0 'kann optimiert werden wahr '. Bei der Betrachtung vieler optimierter Assemblierungen von mehreren Compilern habe ich nie irgendwelche vernünftigen Muster gesehen, was Optimierer verstehen oder nicht verstehen. – JSF

Antwort

15

Der Grund für dieses Verhalten der Vergleichsoperatoren < oder >= unterscheidet, ob Ihr double ist NaN oder kein NaN. Alle Vergleiche, bei denen einer der Operanden ist, geben false zurück. So wird x*x < 0.0immer falsch sein, unabhängig davon, ob xNaN ist oder nicht. Der Compiler kann das also sicher optimieren. Der Fall x * x >= 0 wird sich jedoch für die Werte NaN und nicht NaN anders verhalten, sodass der Compiler die bedingten Sprünge in der Assembly belässt.

Dies ist, was cppreference sagt über beteiligt mit NaNs Vergleich:

die Werte der Operanden nach der Umwandlung in der üblichen mathematischen Sinne verglichen werden (außer, dass positive und negative Nullen vergleichen gleich und jeder Vergleich einen NaN Beteiligung (0)

Verwandte Themen