2015-05-12 18 views
10

netto 4.6 RC 64 è due volte più lento come x86 (versione):.NET 4.6 RC 64 è due volte più lento come x86 (versione di rilascio)

considerare questo pezzo di codice:

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); 
    } 
} 

Sulla mia macchina, la versione di rilascio x86 impiega 4,5 secondi per essere completata, mentre la x64 impiega 9,5 secondi. C'è qualche bandiera/impostazione specifica necessaria per x64?

UPDATE

Si scopre che RyuJIT ha un ruolo in questo numero. Se useLegacyJit è abilitato in app.config, il risultato è diverso e questa volta x64 è più veloce.

<?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

Ora il problema è stato segnalato al team CLR coreclr, issue 993

+0

Non ho dimestichezza con le norme spettrali, e questa è una discreta quantità di codice da prendere in considerazione. Potresti fornirci un riassunto di ciò che sta facendo - centinaia o migliaia di operazioni con matrici di grandi matrici a virgola mobile con radici quadrate e divisioni anche lì da qualche parte? Puoi profilare questo in entrambi, puoi guardare all'assemblatore generato per eventuali pessimitazioni evidenti? – Rup

+4

Stai eseguendo una versione di rilascio e non la stai eseguendo in un debugger? –

+0

Vale la pena eseguirlo alcune volte in un ciclo 'for' e scartando le prime iterazioni dal momento che il compilatore JIT ha bisogno di eseguire la sua magia la prima volta. –

risposta

4

La ragione per la regressione perf trova risposta in GitHub; brevemente, sembra di riproporlo solo su Intel e non su macchine Amd64. funzionamento ad anello interno

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

risultati 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 fa una scrittura parziale inferiori 8-byte con byte superiore del registro xmm non modificato. Per il caso di riproduzione xmm1 è parzialmente scritto ma ci sono ulteriori usi di xmm1 nel codice. Questo crea una falsa dipendenza tra cvtsi2sd e altre istruzioni che usano xmm1 che influenza il parallelismo delle istruzioni. Effettivamente modificando codegen di Int a Float cast per emettere un "xorps xmm1, xmm1" prima che cvtsi2sd corregge la regressione perf.

Soluzione: Perf regressione potrebbe anche essere evitato se invertiamo l'ordine degli operandi in operazione di moltiplicazione nei metodi MultiplyAv/MultiplyAvt

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 
    } 
}