2016-01-04 4 views
25

TL; DR: Почему обертывание System.Numerics.Vectors типа дорого, и есть ли что-нибудь, что я могу с этим сделать?Дорогой обернуть System.Numerics.VectorX - почему?

Рассмотрим следующий фрагмент кода:

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

Это JIT в (64):

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

и 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 

Теперь, если я обернуть это в структура, например

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

и изменение GetIt, например.

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

JITted результат все равно точно такие же, как при использовании родных типов непосредственно (в AddThem, а SomeWrapper перегруженного оператора и конструктор все встраиваемый). Как и ожидалось.

Теперь, если я попробую это с типами с поддержкой SIMD, например. System.Numerics.Vector4:

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

он JITted в:

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 

Однако, если я завернуть в Составляя Vector4 (по аналогии с первым примером):

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

мой код теперь JITted в намного больше:

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 

Похоже, что JIT теперь решил по какой-то причине не просто использовать регистры, а вместо этого использовать временные переменные, но я не могу понять, почему. Сначала я подумал, что это может быть проблема с выравниванием, но тогда я не понимаю, почему она сначала загружает оба в xmm0, а затем решает обойти поездку в память.

Что здесь происходит? И что еще более важно, могу ли я исправить это?

Причина, по которой я хотел бы обернуть структуру, похожую на то, что у меня есть лот устаревшего кода, который использует API, реализация которого выиграет от некоторой добротности SIMD.

EDIT: Итак, после некоторого копаться в coreclr source, я узнал, что на самом деле ничего особенного классов System.Numerics. Мне просто нужно добавить атрибут System.Numerics.JitIntrinsic к моим методам. Затем JIT заменит мою реализацию своей собственной. JitIntrinsic является частной? Нет проблем, просто скопируйте + вставьте его. Первоначальный вопрос все еще остается (хотя, если у меня есть обходной путь).

ответ

0

Проблема возникает только из-за того, что Vector4 содержит 4 longs, а DirectX Vector4 содержит 4 Floats.В каждом случае прохождение векторов только для добавления Xs делает код намного более сложным, потому что W, Y и Z должны быть скопированы, даже если они не изменяются. Векторы копируются во время каждого «нового SomeWrapper (v)» и вне функции в последний раз влияют на результат на переменную.

Оптимизация кода структуры очень сложная. С struct вы сохраняете время выделения кучи, но из-за нескольких копий код становится более длинным.

Две вещи могут помочь вам:

1) Не используйте обертки, но методы расширения избежать копирования в обертку.

2) Не выделяйте новые векторы для возврата значений, но используйте их, когда это возможно (оптимизируйте код, но не помогите сделать инвариант типа, как и другие типы арифметики, поэтому используйте с особой осторожностью).

Пример:

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

Все поля являются поплавками. Структурная упаковка встроена, за исключением случая SIMD. Код в вашем примере обычно не нужен. Мой вопрос, почему он ломается в случае SIMD? (Как я писал в своем обновлении, мне удалось найти приемлемое обходное решение) – Krumelur

1

Низкая производительность при оберточной Numerics.Vector был вопрос составитель и исправление было совершено освоить на 20 января 2017:

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

Я не знаю, как распространение работает именно в этом проекте, но похоже, что исправление будет частью 2.0.0 release.