Переместите int64_t в старшие четырехслойные слова вектора AVX2 __m256i - PullRequest
0 голосов
/ 05 января 2019

Этот вопрос похож на [1]. Однако я не совсем понял, как он решает вставку в старшие четырехслойные слова ymm с помощью георадара. Кроме того, я хочу, чтобы операция не использовала промежуточные обращения к памяти.

Можно ли это сделать с AVX2 или ниже (у меня нет AVX512)?

[1] Как переместить удвоение в% rax в определенную позицию qword на% ymm или% zmm? (Озеро Кабы или позже)

1 Ответ

0 голосов
/ 05 января 2019

Мой ответ на связанный вопрос не показал способа сделать это, потому что это невозможно сделать очень эффективно без AVX512F для маскированной трансляции (vpbroadcastq zmm0{k1}, rax). Но на самом деле это не так уж и плохо с использованием чистого регистра, примерно такой же стоимости, как vpinsrq + немедленное смешивание.

(В Intel всего 3 моп. 2 моп для порта 5 (vmovq + широковещание) и немедленное смешивание, которое может работать на любом порту. Смотри https://agner.org/optimize/).

Я обновил свой ответ там с помощью asm для этого. В C ++ со встроенными функциями Intel вы должны сделать что-то вроде:

#include <immintrin.h>
#include <stdint.h>

// integer version.  An FP version would still use _mm256_set1_epi64x, then a cast
template<unsigned elem>
static inline
__m256i merge_epi64(__m256i v, int64_t newval)
{
    static_assert(elem <= 3, "a __m256i only has 4 qword elements");

    __m256i splat = _mm256_set1_epi64x(newval);

    constexpr unsigned dword_blendmask = 0b11 << (elem*2);  // vpblendd uses 2 bits per qword
    return  _mm256_blend_epi32(v, splat, dword_blendmask);
}

Clang компилирует это почти идеально эффективно для всех 4-х возможных позиций элементов, что действительно показывает, насколько хорош его оптимизатор шаффла. Это использует все особые случаи. И в качестве бонуса, он комментирует свой ассемблер, чтобы показать вам, какие элементы откуда и где смешиваются и перемешиваются.

Из проводника компилятора Godbolt некоторые тестовые функции, чтобы увидеть, что происходит с аргументами в regs.

__m256i merge3(__m256i v, int64_t newval) {
    return merge_epi64<3> (v, newval);
}
// and so on for 2..0

# clang7.0 -O3 -march=haswell
merge3(long long __vector(4), long):
    vmovq   xmm1, rdi
    vpbroadcastq    ymm1, xmm1
    vpblendd        ymm0, ymm0, ymm1, 192 # ymm0 = ymm0[0,1,2,3,4,5],ymm1[6,7]
                      # 192 = 0xC0 = 0b11000000
    ret

merge2(long long __vector(4), long):
    vmovq   xmm1, rdi
    vinserti128     ymm1, ymm0, xmm1, 1          # Runs on more ports than vbroadcast on AMD Ryzen
        #  But it introduced a dependency on  v (ymm0) before the blend for no reason, for the low half of ymm1.  Could have used xmm1, xmm1.
    vpblendd        ymm0, ymm0, ymm1, 48 # ymm0 = ymm0[0,1,2,3],ymm1[4,5],ymm0[6,7]
    ret

merge1(long long __vector(4), long):
    vmovq   xmm1, rdi
    vpbroadcastq    xmm1, xmm1           # only an *XMM* broadcast, 1c latency instead of 3.
    vpblendd        ymm0, ymm0, ymm1, 12 # ymm0 = ymm0[0,1],ymm1[2,3],ymm0[4,5,6,7]
    ret

merge0(long long __vector(4), long):
    vmovq   xmm1, rdi
           # broadcast optimized away, newval is already in the low element
    vpblendd        ymm0, ymm0, ymm1, 3 # ymm0 = ymm1[0,1],ymm0[2,3,4,5,6,7]
    ret

Другие компиляторы вслепую транслируют полный YMM, а затем смешивают, даже для elem = 0. Вы можете специализировать шаблон или добавить в шаблон условия if(), которые будут оптимизированы. Например. splat = (elem?) set1() : v; чтобы сохранить трансляцию для элем == 0. Если хотите, вы можете использовать и другие оптимизации.


В GCC 8.x и более ранних версиях обычно используется неправильный способ передачи целого числа: они сохраняются / перезагружаются. Это позволяет избежать использования любых портов ALU shuffle, поскольку широковещательная загрузка на процессорах Intel бесплатна, но при этом вводится задержка пересылки хранилища в цепочке от целого числа до конечного векторного результата.

Это исправлено в текущей транке для gcc9, но я не знаю, есть ли обходной путь для получения глупого кода с более ранним gcc. Обычно -march=<an intel uarch> предпочитает ALU вместо сохранения / перезагрузки для целого числа -> вектор и наоборот, но в этом случае модель затрат по-прежнему выбирает сохранение / перезагрузку с -march=haswell.

# gcc8.2 -O3 -march=haswell
merge0(long long __vector(4), long):
    push    rbp
    mov     rbp, rsp
    and     rsp, -32          # align the stack even though no YMM is spilled/loaded
    mov     QWORD PTR [rsp-8], rdi
    vpbroadcastq    ymm1, QWORD PTR [rsp-8]   # 1 uop on Intel
    vpblendd        ymm0, ymm0, ymm1, 3
    leave
    ret

; GCC trunk: g++ (GCC-Explorer-Build) 9.0.0 20190103 (experimental)
; MSVC and ICC do this, too.  (For MSVC, make sure to compile with -arch:AVX2)
merge0(long long __vector(4), long):
    vmovq   xmm2, rdi
    vpbroadcastq    ymm1, xmm2
    vpblendd        ymm0, ymm0, ymm1, 3
    ret

Для позиции элемента переменной времени выполнения перемешивание все еще работает, но вам нужно будет создать вектор маски смешивания с старшим битом, установленным в правом элементе. например с vpmovsxbq нагрузкой от mask[3-elem] в alignas(8) int8_t mask[] = { 0,0,0,-1,0,0,0 };. Но vpblendvb или vblendvpd медленнее, чем немедленная смесь, особенно в Haswell, поэтому по возможности избегайте этого.

...