Есть ли в AVX512 такая функция, как _mm512_sign_epi16 (__m512i a, __m512i b) - PullRequest
4 голосов
/ 18 апреля 2019

Следующая функция, кажется, недоступна на AVX512:

__m512i _mm512_sign_epi16 (__m512i a, __m512i b)

Будет ли он доступен в ближайшее время или есть альтернатива?

Ответы [ 2 ]

5 голосов
/ 18 апреля 2019

Если вам не нужна часть обнуления , вам нужно только 2 инструкции (и регистр обнуления):

Вы можете _mm512_movepi16_mask() подписать биты в маске (версия AVX512 pmovmskb) и сделать вычитание слияния из нуля, чтобы свести к нулю вектор на основе признаков другого.

#ifdef __AVX512BW__
// does *not* do anything special for signs[i] == 0, just negative / non-negative
__m512i  conditional_negate(__m512i target, __m512i signs) {
    __mmask32 negmask = _mm512_movepi16_mask(signs);
      // vpsubw target{k1}, 0, target
    __m512i neg = _mm512_mask_sub_epi16(target, negmask, _mm512_setzero_si512(), target);
    return neg;
}
#endif

vector -> mask имеет 3 задержки цикла на Skylake-X (с vpmovw2m, vptestmw или vpcmpw), но использование маски имеет только еще одну задержку 1 цикла. Таким образом, задержка от входов до выходов:

  • 4 цикла из signs -> результат по SKX
  • 1 цикл из target -> результат на SKX (только замаскированный vpsubw с нуля.)

Чтобы также применить условие is-zero : вы можете быть в состоянии обнулить или слить маску при следующей операции, которую вы выполняете с вектором, поэтому элементы, которые должны были быть равны нулю, не используются.

Вам нужно дополнительное сравнение для создания другой маски, но вам, вероятно, не нужно нужно тратить 2-ю дополнительную инструкцию, чтобы применить ее сразу.

Если вы действительно хотите построить автономный vpsignw таким образом, мы можем выполнить это окончательное маскирование нуля, но это 4 встроенные функции, которые компилируются в 4 инструкции, и, вероятно, хуже для пропускной способности, чем @ wim's min / max / размножаться. Но это имеет хорошую задержку критического пути, всего около 5 циклов на SKX (или 4, если вы можете сложить окончательную маскировку во что-то еще). Критический путь это знаки-> маска, затем маскируемый саб. Знаки-> ненулевая маска могут работать параллельно с любым из них.

__m512i  mm512_psignw(__m512i target, __m512i signs) {
    __mmask32 negmask = _mm512_movepi16_mask(signs);
      // vpsubw target{negmask}, 0, target  merge masking to only modify elements that need negating
    __m512i neg = _mm512_mask_sub_epi16(target, negmask, _mm512_setzero_si512(), target);

    __mmask32 nonzeromask = _mm512_test_epi16_mask(signs,signs);  // per-element non-zero?
    return  _mm512_maskz_mov_epi16(nonzeromask, neg);        // zero elements where signs was zero
}

Возможно, компилятор может сложить эту нулевую маскировку vmovdqu16, встроенную в маскировку слияния для add / or / xor, или нулевую маскировку для умножения / and. Но, вероятно, хорошая идея сделать это самостоятельно.

4 голосов
/ 18 апреля 2019

Возможное решение:

__m512i mm512_sign_epi16(__m512i a, __m512i b){
    /* Emulate _mm512_sign_epi16() with instructions  */
    /* that exist in the AVX-512 instruction set      */
    b = _mm512_min_epi16(b, _mm512_set1_epi16(1));     /* clamp b between -1 and 1 */
    b = _mm512_max_epi16(b, _mm512_set1_epi16(-1));    /* now b = -1, 0 or 1       */
    a = _mm512_mullo_epi16(a, b);                      /* apply the sign of b to a */
    return a;
}

Это решение должно иметь разумную пропускную способность, но задержка может быть неоптимальной из-за целочисленного умножения. Хорошей альтернативой является решение Питера Кордеса , которое имеет большую задержку. Но на практике высокая пропускная способность обычно более интересна, чем низкая задержка.

В любом случае, фактическая производительность различных альтернатив (решение здесь, ответ Питера Кордеса и идея разделения в chtz 'комментарии ) зависит от окружающего кода и типа процессора, который выполняет инструкции. Вам нужно будет сравнить альтернативы, чтобы увидеть, какой из них наиболее быстрый в вашем конкретном случае.

...