2015-05-12 13 views
10

Net 4.6 RC x64 doppelt so langsam wie x86 ist (Release-Version):.NET 4.6 RC x64 ist doppelt so langsam wie x86 (Release-Version)

dieses Stück Code vor:

class SpectralNorm 
{ 
    public static void Main(String[] args) 
    { 
     int n = 5500; 
     if (args.Length > 0) n = Int32.Parse(args[0]); 

     var spec = new SpectralNorm(); 
     var watch = Stopwatch.StartNew(); 
     var res = spec.Approximate(n); 

     Console.WriteLine("{0:f9} -- {1}", res, watch.Elapsed.TotalMilliseconds); 
    } 

    double Approximate(int n) 
    { 
     // create unit vector 
     double[] u = new double[n]; 
     for (int i = 0; i < n; i++) u[i] = 1; 

     // 20 steps of the power method 
     double[] v = new double[n]; 
     for (int i = 0; i < n; i++) v[i] = 0; 

     for (int i = 0; i < 10; i++) 
     { 
      MultiplyAtAv(n, u, v); 
      MultiplyAtAv(n, v, u); 
     } 

     // B=AtA   A multiplied by A transposed 
     // v.Bv /(v.v) eigenvalue of v 
     double vBv = 0, vv = 0; 
     for (int i = 0; i < n; i++) 
     { 
      vBv += u[i] * v[i]; 
      vv += v[i] * v[i]; 
     } 

     return Math.Sqrt(vBv/vv); 
    } 


    /* return element i,j of infinite matrix A */ 
    double A(int i, int j) 
    { 
     return 1.0/((i + j) * (i + j + 1)/2 + i + 1); 
    } 

    /* multiply vector v by matrix A */ 
    void MultiplyAv(int n, double[] v, double[] Av) 
    { 
     for (int i = 0; i < n; i++) 
     { 
      Av[i] = 0; 
      for (int j = 0; j < n; j++) Av[i] += A(i, j) * v[j]; 
     } 
    } 

    /* multiply vector v by matrix A transposed */ 
    void MultiplyAtv(int n, double[] v, double[] Atv) 
    { 
     for (int i = 0; i < n; i++) 
     { 
      Atv[i] = 0; 
      for (int j = 0; j < n; j++) Atv[i] += A(j, i) * v[j]; 
     } 
    } 

    /* multiply vector v by matrix A and then by matrix A transposed */ 
    void MultiplyAtAv(int n, double[] v, double[] AtAv) 
    { 
     double[] u = new double[n]; 
     MultiplyAv(n, v, u); 
     MultiplyAtv(n, u, AtAv); 
    } 
} 

Auf meiner Maschine dauert x86 Release-Version 4,5 Sekunden, während die x64 9,5 Sekunden dauert. Gibt es ein bestimmtes Flag/eine bestimmte Einstellung für das x64?

UPDATE

Es stellt sich heraus, dass RyuJIT eine Rolle in dieser Frage hat. Wenn useLegacyJit in app.config aktiviert ist, ist das Ergebnis anders und dieses Mal ist x64 schneller.

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/> 
    </startup> 
    <runtime> 
    <useLegacyJit enabled="1" /> 
</runtime> 
</configuration> 

UPDATE

Nun ist die Frage wurde auf die CLR-Team berichtet coreclr, issue 993

+0

Ich bin nicht vertraut mit spektralen Normen umgekehrt, und das ist eine ganze Menge Code Erwägen. Könnten Sie uns eine Zusammenfassung dessen geben, was das tut - Hunderte oder Tausende von Matrixoperationen großer Gleitkomma-Doppelmatrizen mit Quadratwurzeln und Unterteilungen dort irgendwo? Kannst du das in beiden Profilen darstellen? Kannst du den generierten Assembler für irgendwelche offensichtlichen Pessimierungen betrachten? – Rup

+4

Führen Sie einen Release-Build aus, und führen Sie ihn nicht in einem Debugger aus? –

+0

Es lohnt sich, es ein paar Mal in einer 'for'-Schleife auszuführen und die ersten paar Iterationen zu ignorieren, da der JIT-Compiler beim ersten Mal seine Magie ausspielen muss. –

Antwort

4

Der Grund für perf Regression auf GitHub beantwortet wird; Kurz, es scheint, nur auf Intel und nicht auf AMD64-Maschinen zu repro. Innenschleifenoperation

Av[i] += v[j] * A(i, j); 

Ergebnisse in

IN002a: 000093 lea  eax, [rax+r10+1] 
IN002b: 000098 cvtsi2sd xmm1, rax 
IN002c: 00009C movsd xmm2, qword ptr [@RWD00] 
IN002d: 0000A4 divsd xmm2, xmm1 
IN002e: 0000A8 movsxd eax, edi 
IN002f: 0000AB movaps xmm1, xmm2 
IN0030: 0000AE mulsd xmm1, qword ptr [r8+8*rax+16] 
IN0031: 0000B5 addsd xmm0, xmm1 
IN0032: 0000B9 movsd qword ptr [rbx], xmm0 

Cvtsi2sd funktioniert ein partielles Schreiben der unteren 8-Byte mit den oberen Bytes des XMM-Register nicht modifiziert. Für den Reprofall wird xmm1 teilweise geschrieben, aber es gibt weitere Verwendungen von xmm1 im Code. Dies erzeugt eine falsche Abhängigkeit zwischen cvtsi2sd und anderen Anweisungen, die xmm1 verwenden, was die Befehlsparallelität beeinflusst. In der Tat modifiziert Codegen von Int in Float umgewandelt, um ein "xorps xmm1, xmm1" zu emittieren, bevor cvtsi2sd die Perf-Regression korrigiert.

Umgehung: Perf Regression auch vermieden werden könnte, wenn wir die Reihenfolge der Operanden in Multiplikationsoperation in MultiplyAv/MultiplyAvt Methoden

void MultiplyAv(int n, double[] v, double[] Av) 
{ 
    for (int i = 0; i < n; i++) 
    { 
     Av[i] = 0; 
     for (int j = 0; j < n; j++) 
       Av[i] += v[j] * A(i, j); // order of operands reversed 
    } 
}