c# simd ryujit

c# - Caro para envolver System.Numerics.VectorX-¿por qué?



simd ryujit (2)

TL; DR : ¿Por qué está ajustando el tipo System.Numerics.Vectors caro, y hay algo que pueda hacer al respecto?

Considere la siguiente pieza de código:

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

Esto JIT en (x64):

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

y 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

Ahora, si envuelvo esto en una estructura, por ejemplo

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

y cambiar GetIt , por ejemplo

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

El resultado de JITted sigue siendo exactamente el mismo que cuando se usan los tipos nativos directamente (el AddThem y el operador y constructor sobrecargados de SomeWrapper están todos en línea). Como se esperaba.

Ahora, si intento esto con los tipos habilitados para SIMD, por ejemplo, System.Numerics.Vector4 :

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

se introduce en:

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

Sin embargo, si Vector4 el Vector4 en una estructura (similar al primer ejemplo):

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

Mi código ahora está JITted en mucho más:

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

Parece que el JIT ahora ha decidido por alguna razón que no puede usar solo los registros, y en su lugar usa variables temporales, pero no puedo entender por qué. Primero pensé que podría tratarse de un problema de alineación, pero luego no puedo entender por qué primero se cargan en xmm0 y luego se deciden por un viaje de ida y vuelta a la memoria.

¿Que esta pasando aqui? Y lo más importante, ¿puedo arreglarlo?

La razón por la que me gustaría envolver la estructura de esta manera es que tengo un montón de código heredado que utiliza una API cuya implementación se beneficiaría de la bondad de SIMD.

EDIT : Entonces, después de investigar un poco en la fuente principal , descubrí que en realidad no es nada especial acerca de las clases de System.Numerics. Solo tengo que agregar el atributo System.Numerics.JitIntrinsic a mis métodos. El JIT reemplazará mi implementación con la suya propia. JitIntrinsic es privado? No hay problema, solo copia y pégalo. La pregunta original aún permanece sin embargo (incluso si ahora tengo una solución).


El problema viene del hecho de que un Vector4 contiene 4 largos y DirectX Vector4 contiene 4 Flotadores. En cada caso, pasar los vectores solo para agregar X hace que el código sea mucho más complejo porque W, Y y Z tienen que ser copiados incluso si no se modifican. Los vectores se copian durante cada "nuevo SomeWrapper (v)" y fuera de la función una última vez para afectar el resultado a la variable.

Optimizar el código de la estructura es muy complicado. Con struct, ahorras tiempo de asignación de pila, pero debido a varias copias, el código se vuelve más largo.

Dos cosas pueden ayudarte:

1) No utilice envoltorios, pero los métodos de extensión evitan la copia en el envoltorio.

2) No asigne nuevos vectores a valores de retorno, pero use uno de ellos cuando sea posible (optimice el código pero no ayude a hacer que el tipo sea invariable, como otros tipos aritméticos, así que use con extrema precaución).

Muestra:

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


Rendimiento deficiente al ajustar Numerics.Vector fue un problema del compilador y la solución se comprometió a dominar el 20 de enero de 2017:

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

No sé cómo funciona exactamente la propagación en este proyecto, pero parece que la solución será parte de la versión 2.0.0 .