Условно перевернутый знак float с помощью SSE и / или AVX

#c #optimization #sse #avx #scramble

#c #оптимизация #sse #avx #скремблирование

Вопрос:

Если bitchar[] это массив из 0 и 1, я хочу перевернуть знак in[i] if bitchar[i] = 1 (скремблирование):

 float *in = get_in();
float *out = get_out();
char *bitchar = get_bitseq();
for (int i = 0; i < size; i  ) {
   out[i] = in[i] * (1 - 2 * bitchar[i]);
}
 

Мой код AVX:

 __m256 xmm1 = _mm256_set_ps(1);
__m256 xmm2 = _mm256_set_ps(2);
for (int i = 0; i < size; i =8) {
   __m256 xmmb = _mm256_setr_ps (bitchar[i 7], bitchar[i 6], bitchar[i 5], bitchar[i 4], bitchar[i 3], bitchar[i 2], bitchar[i 1], bitchar[i]);
   __m256 xmmpm1 = _mm256_sub_ps(xmm1, _mm256_mul_ps(xmm2,xmmb));
   __m256 xmmout = _mm256_mul_ps(_mm256_load_ps(amp;in[i]),xmmpm1);
   _mm256_store_ps(amp;out[i],xmmout);
}
 

Однако код AVX не намного быстрее, иногда даже медленнее. Возможно, мой avx не является оптимальным. Кто-нибудь может мне помочь?

Комментарии:

1. чтобы переключить знак, вам просто нужно XOR значение с 0x80000000, используя _mm256_xor_ps вместо множества подобных инструкций

2. @AdrianMole да, это так in[i]

3. @phuclv спасибо, я согласен. Однако прием xor зависит от того, как бит знака реализован в конкретной архитектуре. Я буду использовать его в качестве последнего средства.

4. @AnnaNoie AVX всегда использует IEEE-754, поэтому другого лучшего способа нет. Поскольку вы уже используете встроенные функции, нет никаких причин писать переносимый код, не зависящий от формата

5. Если вы храните целые байты, можете ли вы сделать их 0 / -1, чтобы вы могли расширить знак до 32-разрядного с _mm256_cvtepi8_epi32 помощью , и И вместо shift ? Для этого потребуется отдельная векторная константа, и vpand это только лучше, чем vpslld ymm, ymm, 31 если бы вы делали это в цикле, смешанном с математическими операциями FP, которые конкурировали бы за порты со сдвигом.

Ответ №1:

Поблагодарите всех за подсказки. Я придумал это решение, используя SSE4.1. Любое лучшее решение будет оценено.

     const int size4 = (size / 4) * 4;
    for (int i = 0; i < size4; i  = 4) {
        __m128i xmm1 = _mm_cvtepu8_epi32((__m128i) _mm_loadu_ps((float *) amp;bitchar[i]));
        __m128 xmm2 = (__m128) _mm_slli_epi32(xmm1, 31);
        __m128 xmm3 = _mm_xor_ps(xmm2, _mm_loadu_ps(amp;in[i]));
        _mm_storeu_ps(amp;out[i], xmm3);
    }
    for (int i = size4; i < size; i  ) {
        out[i] = in[i] * (1 - 2 * bitchar[i]);
    }
 

Комментарии:

1. Обычно вы бы использовали _mm_loadu_si128((const __m128i *) amp;bitchar[i]) . (Или на самом деле встроенный для загрузки 4 или 8 байт в __m128i, а не 16 байт, но компиляторы часто отстой при сворачивании таких нагрузок в источник памяти для vpmovzxbd ), И вы могли бы использовать _mm256_cvtepu8_epi32 для преобразования для распаковки в 256-битный вектор, если у вас есть AVX2, а также AVX.

2. @PeterCordes спасибо. Я переключусь на _mm_loadu_si128 . И на моей целевой машине нет AVX2.