самый быстрый шаг 2 собрать - PullRequest
1 голос
/ 11 июля 2020

Я знаю, что был вопрос о быстрой сборке stride-3 с AVX2. Мне интересно, какова самая быстрая последовательность сбора шагов 2, скажем, я хочу загрузить все нечетные элементы вектора длиной 16 в ymm0.

В частности, меня интересуют относительные преимущества и затраты

  1. с использованием сборки AVX2 с шагом 2 и
  2. выдачей двух векторных загрузок, а затем с использованием последовательности инструкций смешивания и перемешивания.

Если 2) - это всегда лучше, чем 1), какая последовательность инструкций лучше всего использовать?

1 Ответ

1 голос
/ 12 июля 2020

Поскольку vshufps и vpermps оба выполняются на порту 5 (Intel Skylake), я бы предпочел vblendps + vpermps, а не vshufps + vpermps, для лучшего сочетания инструкций. На Intel skylake vblendps может выполняться на порте 0, 1 или 5. Следующее решение использует 2 перекрывающиеся векторные загрузки:

#include <stdio.h>
#include <immintrin.h>

__m256 stide_2_load_odd(float * a){
    __m256 x_lo = _mm256_loadu_ps(&a[1]);
    __m256 x_hi = _mm256_loadu_ps(&a[8]);
    __m256 x_b = _mm256_blend_ps(x_lo, x_hi, 0b10101010);
    __m256 y = _mm256_permutevar8x32_ps(x_b, _mm256_set_epi32(7,5,3,1,6,4,2,0));
    return y;
}

__m256 stide_2_load_even(float * a){
    __m256 x_lo = _mm256_loadu_ps(&a[0]);
    __m256 x_hi = _mm256_loadu_ps(&a[7]);
    __m256 x_b = _mm256_blend_ps(x_lo, x_hi, 0b10101010);
    __m256 y = _mm256_permutevar8x32_ps(x_b, _mm256_set_epi32(7,5,3,1,6,4,2,0));
    return y;
}


int main()
{
    float a[] = {0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1, 9.1, 10.1, 11.1, 12.1, 13.1, 14.1, 15.1};
    float b[8];
    __m256 y = stide_2_load_odd(a);
    _mm256_storeu_ps(b, y);
    printf("odd indices 1, 3, 5, ...\n");
    for(int i=0; i<8; i++){
        printf("y[%i] = %f \n", i, b[i]);
    }
    y = stide_2_load_even(a);
    _mm256_storeu_ps(b, y);
    printf("\neven indices 0, 2, 4, ...\n");
    for(int i=0; i<8; i++){
        printf("y[%i] = %f \n", i, b[i]);
    }
    return 0;
}

Результат:

$gcc -Wall -O3 -march=skylake -o main *.c


$main
odd indices 1, 3, 5, ...
y[0] = 1.100000 
y[1] = 3.100000 
y[2] = 5.100000 
y[3] = 7.100000 
y[4] = 9.100000 
y[5] = 11.100000 
y[6] = 13.100000 
y[7] = 15.100000 

even indices 0, 2, 4, ...
y[0] = 0.100000 
y[1] = 2.100000 
y[2] = 4.100000 
y[3] = 6.100000 
y[4] = 8.100000 
y[5] = 10.100000 
y[6] = 12.100000 
y[7] = 14.100000 

Здесь невыровненные нагрузки используются. На современных процессорах это не приводит к снижению производительности, если операция чтения из памяти не пересекает границу строки кэша. Поэтому предпочтительно вызывать эти две функции с адресом, выровненным по 64 байта a. См. Также комментарий Питера Кордеса .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...