Собирать / разбрасывать 16-битные целые числа с помощью AVX-512

Я пытался понять, как мы должны разбросать 16-битные целые числа, используя инструкции разброса в AVX512. У меня есть 8 x 16-битных целых чисел, хранящихся по одному в каждом из 32-битных целых чисел __m256i. Я бы использовал 256-битный эквивалент _mm512_i32extscatter_epi32, понижающий преобразование _MM_DOWNCONV_EPI32_UINT16, но такой инструкции нет, и понижающее преобразование не работает на AVX512.

Насколько я понимаю, мы должны выполнять 32-битные операции чтения и записи, и мы должны быть осторожны, чтобы две соседние 16-битные записи уничтожали друг друга (если один и тот же индекс в списке индексов дважды, тогда я не не нужно беспокоиться о том, что произойдет раньше). Таким образом, мы должны использовать цикл разброса при сборе данных о конфликтах. В цикле мы должны конфликтовать с 32-битными целочисленными адресами или с 16-битными индексами, сдвинутыми влево на 1 и используемыми в качестве индексов для эквивалентного 32-битного массива (эквивалент преобразования 16-битного массива в 32-битный массив). array, а затем разделив индекс на 2). Затем нам нужно взять 32-битное целое число, которое мы читаем, и изменить старшие 16 бит или младшие 16 бит в зависимости от того, был ли исходный индекс в 16-битном массиве нечетным или четным.

Итак, вот что я получаю:

  1. Определите, являются ли индексы нечетными или четными, и установите 2-битную маску 01 или 10 соответственно, образуя 16-битную маску для 8 целых чисел.

  2. Превратите 16-битные целые числа в 32-битные целые числа, скопировав младшие 16 бит в старшие 16 бит.

  3. Превратите индекс в массив 16-битных целых чисел в индекс в массив 32-битных индексов, сдвинувшись вправо на единицу.

  4. Используйте конфликтную петлю с маской

  5. 32-битные целые числа с маскированной сборкой

  6. Используйте _mm256_mask_blend_epi16, чтобы выбрать, следует ли изменять старшие или младшие 16 бит только что прочитанных 32-битных целых чисел (используя маску из (1)).

  7. Замаскированный-рассеянный обратно в память

  8. повторять до тех пор, пока у нас не будет конфликтов в незаписанных 32-битных целочисленных адресах.

Пожалуйста, есть ли более быстрый (или более простой) способ сделать это? И да, я знаю, что отдельные записи выполняются быстрее, но речь идет о том, как это сделать с помощью AVX-512.

Вот код:

void scatter(uint16_t *array, __m256i vindex, __m256i a)
    {
    __mmask16 odd = _mm256_test_epi16_mask(vindex, _mm256_set1_epi32(1));
    __mmask16 even = ~odd & 0x5555;
    __mmask16 odd_even = odd << 1 | even;

    __m256i data = _mm256_mask_blend_epi16(0x5555, _mm256_bslli_epi128(a, 2), a);

    __m256i word_locations = _mm256_srli_epi32(vindex, 1);
    __mmask8 unwritten = 0xFF;
    do
        {
        __m256i conflict = _mm256_maskz_conflict_epi32 (unwritten, word_locations);
        conflict = _mm256_and_si256(_mm256_set1_epi32(unwritten), conflict);
        __mmask8 mask = unwritten & _mm256_testn_epi32_mask(conflict, _mm256_set1_epi32(0xFFFF'FFFF));

        __m256i was = _mm256_mmask_i32gather_epi32(_mm256_setzero_si256(), mask, word_locations, array, 4);
        __m256i send = _mm256_mask_blend_epi16(odd_even, was, data);
        _mm256_mask_i32scatter_epi32(array, mask, word_locations, send, 4);

        unwritten ^= mask;
        }
    while (unwritten != 0);
    }

person Andrew Trotman    schedule 05.06.2020    source источник
comment
Вы делаете это один раз с 8 значениями или сотни раз? Можно ли переключить хранилище на 32-битное? Если вы не можете исключить перекрывающиеся 32-битные хранилища, я не понимаю, как вы можете добиться большего, чем 2-кратный сбор и 2-кратный разброс (ни один из них не намного быстрее, чем отдельные загрузки / хранилища).   -  person chtz    schedule 05.06.2020
comment
Мне нужно сделать это тысячи раз, а также с 8-битными значениями. Я не могу использовать 32-битные целые числа, так как это увеличивает мой массив, что отрицательно сказывается на времени выполнения из-за уменьшения количества попаданий в кеш.   -  person Andrew Trotman    schedule 06.06.2020


Ответы (1)


Если безопасно читать / писать в два байта после последнего индекса, это также должно работать:

void scatter2(uint16_t *array, __m256i vindex, __m256i a) {
  __mmask8 odd = _mm256_test_epi32_mask(vindex, _mm256_set1_epi32(1));

  int32_t* arr32 = (int32_t*)array;
  __m256i was_odd = _mm256_i32gather_epi32(arr32, vindex, 2);

  __m256i data_even = _mm256_mask_blend_epi16(0x5555, was_odd, a);
  _mm256_mask_i32scatter_epi32(array, ~odd, vindex, data_even, 2);
  __m256i was_even = _mm256_i32gather_epi32(arr32, vindex, 2);

  __m256i data_odd = _mm256_mask_blend_epi16(0x5555, was_even, a);
  _mm256_mask_i32scatter_epi32(array, odd, vindex, data_odd, 2);
}

Если вы можете гарантировать, что индексы в vindex увеличиваются (или, по крайней мере, для любых частично конфликтующих {i, i+1} в vindex i+1 идет после i), вы, вероятно, сможете обойтись одним сбором + смешением + разбросом. Кроме того, может быть полезно использовать маскированные сборки (т.е. каждый раз собирать только те элементы, которые вы перезаписываете следующим) - я не уверен, влияет ли это на пропускную способность. Наконец, _mm256_mask_blend_epi16 можно было бы заменить простым _mm256_blend_epi16.

person chtz    schedule 07.06.2020