2016-01-04 9 views
25

TL; DR: Warum ist das Einpacken der System.Numerics.Vectors teuer, und kann ich irgendetwas dagegen tun?Teuer, System.Numerics.VectorX zu wickeln - warum?

Betrachten Sie das folgende Stück Code:

[MethodImpl(MethodImplOptions.NoInlining)] 
private static long GetIt(long a, long b) 
{ 
    var x = AddThem(a, b); 
    return x; 
} 

private static long AddThem(long a, long b) 
{ 
    return a + b; 
} 

Dies wird JIT in (x64):

00007FFDA3F94500 lea   rax,[rcx+rdx] 
00007FFDA3F94504 ret 

und x86:

00EB2E20 push  ebp 
00EB2E21 mov   ebp,esp 
00EB2E23 mov   eax,dword ptr [ebp+10h] 
00EB2E26 mov   edx,dword ptr [ebp+14h] 
00EB2E29 add   eax,dword ptr [ebp+8] 
00EB2E2C adc   edx,dword ptr [ebp+0Ch] 
00EB2E2F pop   ebp 
00EB2E30 ret   10h 

Nun, wenn ich diese umwickeln eine Struktur, z

public struct SomeWrapper 
{ 
    public long X; 
    public SomeWrapper(long X) { this.X = X; } 
    public static SomeWrapper operator +(SomeWrapper a, SomeWrapper b) 
    { 
     return new SomeWrapper(a.X + b.X); 
    } 
} 

und GetIt verändern, z.B.

private static long GetIt(long a, long b) 
{ 
    var x = AddThem(new SomeWrapper(a), new SomeWrapper(b)).X; 
    return x; 
} 
private static SomeWrapper AddThem(SomeWrapper a, SomeWrapper b) 
{ 
    return a + b; 
} 

das JITted Ergebnis ist immer noch genau das gleiche wie wenn sie direkt die nativen Typen (die AddThem und der SomeWrapper überladenen Operator und Konstruktor sind alle inlined). Wie erwartet.

Nun, wenn ich dies mit den SIMD-fähigen Typen, z. System.Numerics.Vector4:

[MethodImpl(MethodImplOptions.NoInlining)] 
private static Vector4 GetIt(Vector4 a, Vector4 b) 
{ 
    var x = AddThem(a, b); 
    return x; 
} 

es in JITted ist:

00007FFDA3F94640 vmovupd  xmm0,xmmword ptr [rdx] 
00007FFDA3F94645 vmovupd  xmm1,xmmword ptr [r8] 
00007FFDA3F9464A vaddps  xmm0,xmm0,xmm1 
00007FFDA3F9464F vmovupd  xmmword ptr [rcx],xmm0 
00007FFDA3F94654 ret 

Allerdings, wenn ich die Vector4 in einer Struktur (ähnlich dem ersten Beispiel) wickeln:

public struct SomeWrapper 
{ 
    public Vector4 X; 

    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public SomeWrapper(Vector4 X) { this.X = X; } 

    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static SomeWrapper operator+(SomeWrapper a, SomeWrapper b) 
    { 
     return new SomeWrapper(a.X + b.X); 
    } 
} 
[MethodImpl(MethodImplOptions.NoInlining)] 
private static Vector4 GetIt(Vector4 a, Vector4 b) 
{ 
    var x = AddThem(new SomeWrapper(a), new SomeWrapper(b)).X; 
    return x; 
} 

mein Code ist jetzt JITted in eine ganze Menge mehr:

00007FFDA3F84A02 sub   rsp,0B8h 
00007FFDA3F84A09 mov   rsi,rcx 
00007FFDA3F84A0C lea   rdi,[rsp+10h] 
00007FFDA3F84A11 mov   ecx,1Ch 
00007FFDA3F84A16 xor   eax,eax 
00007FFDA3F84A18 rep stos dword ptr [rdi] 
00007FFDA3F84A1A mov   rcx,rsi 
00007FFDA3F84A1D vmovupd  xmm0,xmmword ptr [rdx] 
00007FFDA3F84A22 vmovupd  xmmword ptr [rsp+60h],xmm0 
00007FFDA3F84A29 vmovupd  xmm0,xmmword ptr [rsp+60h] 
00007FFDA3F84A30 lea   rax,[rsp+90h] 
00007FFDA3F84A38 vmovupd  xmmword ptr [rax],xmm0 
00007FFDA3F84A3D vmovupd  xmm0,xmmword ptr [r8] 
00007FFDA3F84A42 vmovupd  xmmword ptr [rsp+50h],xmm0 
00007FFDA3F84A49 vmovupd  xmm0,xmmword ptr [rsp+50h] 
00007FFDA3F84A50 lea   rax,[rsp+80h] 
00007FFDA3F84A58 vmovupd  xmmword ptr [rax],xmm0 
00007FFDA3F84A5D vmovdqu  xmm0,xmmword ptr [rsp+90h] 
00007FFDA3F84A67 vmovdqu  xmmword ptr [rsp+40h],xmm0 
00007FFDA3F84A6E vmovdqu  xmm0,xmmword ptr [rsp+80h] 
00007FFDA3F84A78 vmovdqu  xmmword ptr [rsp+30h],xmm0 
00007FFDA3F84A7F vmovdqu  xmm0,xmmword ptr [rsp+40h] 
00007FFDA3F84A86 vmovdqu  xmmword ptr [rsp+20h],xmm0 
00007FFDA3F84A8D vmovdqu  xmm0,xmmword ptr [rsp+30h] 
00007FFDA3F84A94 vmovdqu  xmmword ptr [rsp+10h],xmm0 
00007FFDA3F84A9B vmovups  xmm0,xmmword ptr [rsp+20h] 
00007FFDA3F84AA2 vmovups  xmm1,xmmword ptr [rsp+10h] 
00007FFDA3F84AA9 vaddps  xmm0,xmm0,xmm1 
00007FFDA3F84AAE lea   rax,[rsp] 
00007FFDA3F84AB2 vmovupd  xmmword ptr [rax],xmm0 
00007FFDA3F84AB7 vmovdqu  xmm0,xmmword ptr [rsp] 
00007FFDA3F84ABD vmovdqu  xmmword ptr [rsp+70h],xmm0 
00007FFDA3F84AC4 vmovups  xmm0,xmmword ptr [rsp+70h] 
00007FFDA3F84ACB vmovupd  xmmword ptr [rsp+0A0h],xmm0 
00007FFDA3F84AD5 vmovupd  xmm0,xmmword ptr [rsp+0A0h] 
00007FFDA3F84ADF vmovupd  xmmword ptr [rcx],xmm0 
00007FFDA3F84AE4 add   rsp,0B8h 
00007FFDA3F84AEB pop   rsi 
00007FFDA3F84AEC pop   rdi 
00007FFDA3F84AED ret 

Es sieht so aus, als ob das JIT aus irgendeinem Grund entschieden hat, dass es nicht nur die Register verwenden kann, sondern temporäre Variablen verwendet, aber ich verstehe nicht warum. Zuerst dachte ich, es könnte ein Alignment-Problem sein, aber dann kann ich nicht verstehen, warum es zuerst beide in xmm0 lädt und dann entscheidet, in den Speicher zu gehen.

Was ist hier los? Und noch wichtiger, kann ich es reparieren?

Der Grund, dass ich die Struktur wie diese wickeln möchte, ist, dass ich eine Los Legacy-Code, der eine API verwendet, deren Implementierung von einigen SIMD-Güte profitieren würde.

EDIT: Also, nachdem ich einige Graben um im coreclr source, fand ich heraus, dass es eigentlich nichts Besonderes an den System.Numerics Klassen ist. Ich muss nur das System.Numerics.JitIntrinsic Attribut zu meinen Methoden hinzufügen. Das JIT wird dann meine Implementierung durch eine eigene ersetzen. JitIntrinsic ist privat? Kein Problem, einfach kopieren und einfügen. Die ursprüngliche Frage bleibt jedoch bestehen (auch wenn ich jetzt einen Workaround habe).

Antwort

0

Das Problem kommt nur von der Tatsache, dass ein Vector4 4 Longs enthält und DirectX Vector4 4 Floats enthält.In jedem Fall macht das Übergeben von Vektoren, nur um Xs hinzuzufügen, den Code viel komplexer, weil W, Y und Z kopiert werden müssen, selbst wenn sie unverändert sind. Die Vektoren werden während jedes "neuen SomeWrappers (v)" und außerhalb der Funktion ein letztes Mal kopiert, um das Ergebnis auf die Variable zu beeinflussen.

Die Optimierung des Strukturcodes ist sehr schwierig. Mit struct sparen Sie die Zeit für die Heap-Zuweisung, aber aufgrund mehrerer Kopien wird der Code länger.

Zwei Dinge können Ihnen helfen:

1) Wrapper nicht Erweiterung Verwenden Sie aber Methoden vermeiden, in dem Wrapper kopieren.

2) Ordnen Sie keine neuen Vektoren den Rückgabewerten zu, aber verwenden Sie eine von ihnen, wenn möglich (optimieren Sie den Code, aber machen Sie den Typ nicht invariant, wie andere arithmetische Typen, also mit extremer Vorsicht verwenden).

Probe:

struct Vector 
{ 
    public long X; 
    public long Y; 
} 

static class VectorExtension 
{ 
    public static void AddToMe(this Vector v, long x, long y) 
    { 
     v.X += x; 
     v.Y += y; 
    } 

    public static void AddToMe(this Vector v, Vector v2) 
    { 
     v.X += v2.X; 
     v.Y += v2.Y; 
    } 
} 
+0

Alle Felder sind Floats. Die Strukturumwicklung ist außer im SIMD-Fall inline. Der Code in Ihrem Beispiel wird im Allgemeinen nicht benötigt. Meine Frage ist, warum es im SIMD Fall zusammenbricht? (Wie ich in meinem Update geschrieben habe, konnte ich eine akzeptable Problemumgehung finden) – Krumelur

1

Schlechte Leistung beim Einwickeln Numerics.Vector ein Compiler Problem war, und das Update wurde am 20. Januar 2017 zu meistern verpflichtet:

https://github.com/dotnet/coreclr/issues/7508

Ich weiß nicht, Wie funktioniert die Propagierung genau bei diesem Projekt, aber es scheint so, als wäre der Fix Bestandteil der 2.0.0 release.