Каковы эти дополнительные инструкции по разборке при использовании встроенных SIMD?

Я тестирую, какое ускорение я могу получить, используя инструкции SIMD с RyuJIT, и вижу некоторые инструкции по разборке, которые я не ожидаю. Я основываю код на этом посте в блоге от Кевина Фрея из команды RyuJIT и на соответствующем посте здесь. Вот функция:

static void AddPointwiseSimd(float[] a, float[] b) {
    int simdLength = Vector<float>.Count;
    int i = 0;
    for (i = 0; i < a.Length - simdLength; i += simdLength) {
        Vector<float> va = new Vector<float>(a, i);
        Vector<float> vb = new Vector<float>(b, i);
        va += vb;
        va.CopyTo(a, i);
    }
}

Раздел разборки, который я запрашиваю, копирует значения массива в Vector<float>, Большая часть разборки аналогична той, что была в сообщениях Кевина и Саши, но я выделил некоторые дополнительные инструкции (вместе с моими запутанными аннотациями), которых нет в их разборках:

;// Vector<float> va = new Vector<float>(a, i);
  cmp eax,r8d              ; <-- Unexpected - Compare a.Length to i?
  jae 00007FFB17DB6D5F     ; <-- Unexpected - Jump to range check failure
  lea r10d,[rax+3] 
  cmp r10d,r8d 
  jae 00007FFB17DB6D5F 
  mov r11,rcx              ; <-- Unexpected - Extra register copy?
  movups xmm0,xmmword ptr [r11+rax*4+10h  ]

;// Vector<float> vb = new Vector<float>(b, i);
  cmp eax,r9d              ; <-- Unexpected - Compare b.Length to i?
  jae 00007FFB17DB6D5F     ; <-- Unexpected - Jump to range check failure
  cmp r10d,r9d 
  jae 00007FFB17DB6D5F 
  movups xmm1,xmmword ptr [rdx+rax*4+10h]

Обратите внимание, что проверка диапазона цикла соответствует ожидаемой:

;// for (i = 0; i < a.Length - simdLength; i += simdLength) {
  add eax,4  
  cmp r9d,eax  
  jg loop

так что я не знаю, почему есть дополнительные сравнения с eax, Может кто-нибудь объяснить, почему я вижу эти дополнительные инструкции и возможно ли от них избавиться.

В случае, если это связано с настройками проекта, у меня есть очень похожий проект, который показывает ту же проблему здесь, на github (см. FloatSimdProcessor.HwAcceleratedSumInPlace() или же UShortSimdProcessor.HwAcceleratedSumInPlaceUnchecked()).

1 ответ

Решение

Я буду комментировать генерацию кода, которую я вижу, для процессора, поддерживающего AVX2, такого как Haswell, он может перемещать 8 операций с плавающей точкой за раз:

00007FFA1ECD4E20  push        rsi
00007FFA1ECD4E21  sub         rsp,20h  

00007FFA1ECD4E25  xor         eax,eax                       ; i = 0
00007FFA1ECD4E27  mov         r8d,dword ptr [rcx+8]         ; a.Length
00007FFA1ECD4E2B  lea         r9d,[r8-8]                    ; a.Length - simdLength
00007FFA1ECD4E2F  test        r9d,r9d                       ; if (i >= a.Length - simdLength)
00007FFA1ECD4E32  jle         00007FFA1ECD4E75              ; then skip loop 

00007FFA1ECD4E34  mov         r10d,dword ptr [rdx+8]        ; b.Length
00007FFA1ECD4E38  cmp         eax,r8d                       ; if (i >= a.Length)
00007FFA1ECD4E3B  jae         00007FFA1ECD4E7B              ; then OutOfRangeException
00007FFA1ECD4E3D  lea         r11d,[rax+7]                  ; i+7
00007FFA1ECD4E41  cmp         r11d,r8d                      ; if (i+7 >= a.Length)
00007FFA1ECD4E44  jae         00007FFA1ECD4E7B              ; then OutOfRangeException

00007FFA1ECD4E46  mov         rsi,rcx                       ; move a[i..i+7]
00007FFA1ECD4E49  vmovupd     ymm0,ymmword ptr [rsi+rax*4+10h]  

00007FFA1ECD4E50  cmp         eax,r10d                      ; same as above 
00007FFA1ECD4E53  jae         00007FFA1ECD4E7B              ; but for b
00007FFA1ECD4E55  cmp         r11d,r10d  
00007FFA1ECD4E58  jae         00007FFA1ECD4E7B  
00007FFA1ECD4E5A  vmovupd     ymm1,ymmword ptr [rdx+rax*4+10h]  

00007FFA1ECD4E61  vaddps      ymm0,ymm0,ymm1                ; a[i..] + b[i...]
00007FFA1ECD4E66  vmovupd     ymmword ptr [rsi+rax*4+10h],ymm0  

00007FFA1ECD4E6D  add         eax,8                         ; i += 8
00007FFA1ECD4E70  cmp         r9d,eax                       ; if (i < a.Length)
00007FFA1ECD4E73  jg          00007FFA1ECD4E38              ; then loop

00007FFA1ECD4E75  add         rsp,20h  
00007FFA1ECD4E79  pop         rsi  
00007FFA1ECD4E7A  ret  

Таким образом, eax сравнивает те "надоедливые связанные проверки", о которых говорится в посте блога. Сообщение в блоге дает оптимизированную версию, которая на самом деле не реализована (пока), реальный код прямо сейчас проверяет как первый, так и последний индекс из 8-ми float, которые перемещаются одновременно. Комментарий к сообщению в блоге "Надеемся, что мы добьемся достаточного усиления нашей работы по проверке границ" - это незавершенная задача:)

mov rsi,rcx Инструкция также присутствует в сообщении блога и является ограничением в распределителе регистра. Вероятно, под влиянием того, что RCX является важным регистром, он обычно хранит это. Я полагаю, что недостаточно важного, чтобы выполнить эту работу, чтобы оптимизировать ее, перемещения между регистрами занимают 0 циклов, поскольку они влияют только на переименование регистров.

Обратите внимание, что разница между SSE2 и AVX2 ужасна, хотя код перемещается и добавляет 8 операций с плавающей точкой за раз, фактически он использует только 4 из них. Vector<float>.Count 4 независимо от вкуса процессора, оставляя 2x перфоратора на столе. Трудно скрыть детали реализации, я думаю.

Другие вопросы по тегам