2015-05-12 8 views
10

Net 4.6 RC 64 jest dwa razy tak wolno, jak x86 (wersja release):.NET 4.6 RC 64 jest dwa razy tak wolno, jak x86 (wersja release)

Rozważmy następujący fragment kodu:

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

Na moim komputerze wersja X86 zajmuje 4,5 sekundy, a x64 zajmuje 9,5 sekundy. Czy jest jakaś konkretna flaga/ustawienie potrzebne dla x64?

UPDATE

Okazuje się, że RyuJIT odgrywa rolę w tej kwestii. Jeśli w app.config jest włączone useLegacyJit, wynik jest inny i tym razem x64 jest szybszy.

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

Teraz problem został zgłoszony do zespołu CLR coreclr, issue 993

+0

nie jestem zaznajomiony z normami widmowych, a to sporo kodu rozważać. Czy możesz podać nam podsumowanie tego, co to robi - setki lub tysiące operacji macierzowych dużych podwójnych macierzy o zmiennoprzecinkowej z kwadratowymi korzeniami i podziałami tam gdzieś? Czy możesz to opisać w obu, czy możesz spojrzeć na wygenerowany asembler pod kątem oczywistych pesymisacji? – Rup

+4

Czy używasz kompilacji wydania i nie uruchamiasz go w debugerze? –

+0

Warto uruchomić go kilka razy w pętli 'for' i pomijając kilka pierwszych iteracji, ponieważ kompilator JIT musi działać za pierwszym razem. –

Odpowiedz

4

Powodem jest perf regresji odpowiedział na GitHub; krótko, Wydaje się, że repro tylko na Intel, a nie na Amd64. Pętla wewnętrzna działanie

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

powoduje

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 czyni częściowego zapisu dolnych 8-bajtów z górnymi bajtów Xmm rejestru niezmodyfikowanej. W przypadku repro xmm1 jest częściowo napisane, ale są dalsze zastosowania xmm1 w dół kodu. To tworzy fałszywą zależność między cvtsi2sd i innymi instrukcjami, które używają xmm1, co wpływa na paralelizm instrukcji. Rzeczywiście modyfikując kodegen od Int do Float oddanych do emisji "xorps xmm1, xmm1" przed cvtsi2sd naprawia regresję perf.

Obejście: regresja Perf można również uniknąć, gdyby odwrócić kolejność argumentów w wielowarstwowego pracy w metodach 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 
    } 
} 
Powiązane problemy