Эффективный способ конвертировать индексы индексов в индексы сбора?

Я пытаюсь написать сжатие потока (взять массив и избавиться от пустых элементов) с помощью встроенных SIMD-операций. Каждая итерация цикла обрабатывает 8 элементов за раз (ширина SIMD).

С встроенными функциями SSE я могу сделать это довольно эффективно с помощью _mm_shuffle_epi8(), который выполняет поиск в 16 табличных таблицах (собирается в параллельной вычислительной терминологии). Индексы перемешивания предварительно вычисляются и просматриваются с помощью битовой маски.

for (i = 0; i < n; i += 8)
{
  v8n_Data = _mm_load_si128(&data[i]);
  mask = _mm_movemask_epi8(&is_valid[i]) & 0xff;     // is_valid is byte array
  v8n_Compacted = _mm_shuffle_epi8(v16n_ShuffleIndices[mask]);
  _mm_storeu_si128(&compacted[count], v8n_Compacted);

  count += bitCount[mask];
}

Моя проблема теперь я хотел бы реализовать это для Altivec SIMD тоже (не спрашивайте, почему - ошибочное бизнес-решение). Altivec не имеет эквивалента для _mm_movemask_epi8(), важного ингредиента. Итак, мне нужно будет найти способ либо

  • emulate _mm_movemask_epi8() - кажется дорогим, несколько смен и ORs

  • эффективно генерируют индексы тасования -

а именно, индекс я будет индексом i-го действительного элемента в некомпактных данных

element_valid:   0 0 1 0 1 0 0 1 0
gather_indices:  x x x x x x 6 4 1
scatter_indices: 3 3 2 2 1 1 1 0 0

Просто сделать это серийно, но мне нужно, чтобы он был параллельным (SIMD). Кажется, легко генерировать индексы разброса с префиксом sum, но так как ни AltiVec, ни SSE не имеют команды рассеяния, мне нужно собирать индексы вместо этого. Собирать индексы - это обратная функция индексов рассеяния, но как это можно получить параллельно? Я знаю, что в новаторские дни программирования GPU конвертирование рассеивателей в сборники было распространенным методом, но ни один из этих двух описанных методов не кажется практичным.

Может быть, если не настаивать на том, что уплотнение сохраняет порядок элементов, это обеспечит более эффективную реализацию? Я могу это сделать.

Ответ 1

Если вы хотите эмулировать _mm_movemask_epi8, и вам просто нужна 8-битная скалярная маска из 8-байтовых элементов, вы можете сделать что-то вроде этого с помощью AltiVec:

#include <stdio.h>

int main(void)
{
    const vector unsigned char vShift = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0 };
                                            // constant shift vector

    vector unsigned char isValid = { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
                                            // sample input

    vector unsigned char v1 = vec_sl(isValid, vShift);
                                            // shift input values
    vector unsigned int v2 = vec_sum4s(v1, (vector unsigned int)(0));
    vector signed int v3 = vec_sum2s((vector signed int)v2, (vector signed int)(0));
                                            // sum shifted values
    vector signed int v4 = vec_splat(v3, 1);
    unsigned int mask __attribute__ ((aligned(16)));
    vec_ste((vector unsigned int)v4, 0, &mask);
                                            // store sum in scalar

    printf("v1 = %vu\n", v1);
    printf("v2 = %#vlx\n", v2);
    printf("v3 = %#vlx\n", v3);
    printf("v4 = %#vlx\n", v4);
    printf("mask = %#x\n", mask);

    return 0;
}

Это 5 инструкций AltiVec против 1 в SSE. Возможно, вы потеряете vec_splat и получите его до 4.