Эффективно распакуйте и перетасуйте 8 шорт в руке неон

Представьте, что у меня есть этот входной массив:

uint16_t input[] = { 1, 2, 3, 4, 11, 22, 33, 44 };

как я могу получить из этого входа четыре q-вектора со следующим содержанием:

uint16x8_t q1 = { 11, 11, 1, 1, 11, 11, 1, 1 };
uint16x8_t q2 = { 22, 22, 2, 2, 22, 22, 2, 2 };
uint16x8_t q3 = { 33, 33, 3, 3, 33, 33, 3, 3 };
uint16x8_t q4 = { 44, 44, 4, 4, 44, 44, 4, 4 };

Желательно использовать неоновые присадки?

Решение, которое я придумал, похоже, дает оптимальное arm64, но armv7 кажется не оптимальным. Что может быть улучшено или, возможно, возможен совершенно иной подход?

Обратите внимание, что мой пример кода выводит q1, q2, q3 в некоторую память, в моем реальном коде мне нужно вычислить q1, q2, q3, прежде чем я смогу их использовать.

1 ответ

Решение

Ваша функция слишком сложна.

  • Вам следует избегать vcombine так как это в основном приводит к нескольким ненужным vmov эквиваленты.
  • Вы должны предпочесть vtrn над vzip/vuzp поскольку vtrn намного быстрее

void foo(uint16_t *input, uint16_t *output)
{
    uint16x8_t q0, q1, q2, q3;
    uint16x4x2_t data1, data2, result1, result2;

    data1 = vld2_u16(input);
    data1.val[0] = vrev64_u16(data1.val[0]);
    data1.val[1] = vrev64_u16(data1.val[1]);

    data2 = data1;

    result1 = vtrn_u16(data1.val[0],data2.val[0]);
    result2 = vtrn_u16(data1.val[1],data2.val[1]);

    vst1_u16(&output[0], result1.val[1]);
    vst1_u16(&output[4], result1.val[1]);
    vst1_u16(&output[8], result2.val[1]);
    vst1_u16(&output[12], result2.val[1]);
    vst1_u16(&output[16], result1.val[0]);
    vst1_u16(&output[20], result1.val[0]);
    vst1_u16(&output[24], result2.val[0]);
    vst1_u16(&output[28], result2.val[0]);
}

Это выше должно работать намного быстрее, чем у вас на обоих aarch32 а также aarch64


Рукописные сборки:

aarch32

vld2.16     {d4, d6}, [input]!

vrev64.16   d4, d4
vrev64.16   d6, d6
vmov        d5, d4
vmov        d7, d6
vmov        q0, q2
vmov        q1, q3

vtrn.16     q2, q0
vtrn.16     q3, q1

vst1.16     {q0, q1}, [output]!
vst1.16     {q2, q3}, [output]!

aarch64

ld2     {v2.4h, v3.4h}, [input], #16

rev64   v2.4h, v2.4h
rev64   v3.4h, v3.4h
trn2    v0.4h, v2.4h, v2.4h
trn2    v1.4h, v3.4h, v3.4h
trn1    v2.4h, v2.4h, v2.4h
trn1    v3.4h, v3.4h, v3.4h

mov     v0.d[1], v0.d[0]
mov     v1.d[1], v1.d[0]
mov     v2.d[1], v2.d[0]
mov     v3.d[1], v3.d[0]

st1     {v0.8h-v3.8h}, [output], #64

Тот vrev должно быть выполнено изменений довольно много.

Ниже "дешевое" предложение с использованием vtbl что имеет смысл, если подпрограмма должна выполняться внутри цикла.

void foo(uint16_t *input, uint16_t *output)
{
    uint8_t *pSrc = (uint8_t *) input;
    uint8_t *pDst = (uint8_t *) output;

    uint8x8x2_t data;
    uint8x8_t d0, d1, d2, d3;

    const uint8x8_t mask0 = {8, 9, 8, 9, 0, 1, 0, 1};
    const uint8x8_t mask1 = {10, 11, 10, 11, 2, 3, 2, 3};
    const uint8x8_t mask2 = {12, 13, 12, 13, 4, 5, 4, 5};
    const uint8x8_t mask3 = {14, 15, 14, 15, 6, 7, 6, 7};

    data.val[0] = vld1_u8(pSrc++);
    data.val[1] = vld1_u8(pSrc++);

    d0 = vtbl2_u8(data, mask0);
    d1 = vtbl2_u8(data, mask1);
    d2 = vtbl2_u8(data, mask2);
    d3 = vtbl2_u8(data, mask3);

    vst1_u8(pDst++, d0);
    vst1_u8(pDst++, d0);
    vst1_u8(pDst++, d1);
    vst1_u8(pDst++, d1);
    vst1_u8(pDst++, d2);
    vst1_u8(pDst++, d2);
    vst1_u8(pDst++, d3);
    vst1_u8(pDst++, d3);
}

И последнее, но не менее важное, вероятно, самая быстрая процедура для одной итерации, которая может быть улучшена при написании на ассемблере.

void foo(uint16_t *input, uint16_t *output)
{
    uint16x4x4_t data;

    data = vld4_lane_u16(input++, data, 2);
    data = vld4_lane_u16(input, data, 0);

    data.val[0] = vsli_n_u32(data.val[0], data.val[0], 16);
    data.val[1] = vsli_n_u32(data.val[1], data.val[1], 16);
    data.val[2] = vsli_n_u32(data.val[2], data.val[2], 16);
    data.val[3] = vsli_n_u32(data.val[3], data.val[3], 16);

    vst1_u16(output++, data.val[0]);
    vst1_u16(output++, data.val[0]);
    vst1_u16(output++, data.val[1]);
    vst1_u16(output++, data.val[1]);
    vst1_u16(output++, data.val[2]);
    vst1_u16(output++, data.val[2]);
    vst1_u16(output++, data.val[3]);
    vst1_u16(output, data.val[3]);
}

aarch32

vld4.16     {d2[2], d3[2], d4[2], d5[2]}, [input]!
vld4.16     {d2[0], d3[0], d4[0], d5[0]}, [input]

vsli.32     q1, q1, #16
vsli.32     q2, q2, #16

vmov        q0, q1
vmov        q3, q2

vswp        d1, d2
vswp        d6, d5

vst1.16     {q0, q1}, [output]!
vst1.16     {q2, q3}, [output]

aarch64

ld4     {v0.h, v1.h, v2.h, v3.h}[2], [input], #8
ld4     {v0.h, v1.h, v2.h, v3.h}[0], [input]

sli     v0.2s, v0.2s, #16
sli     v1.2s, v1.2s, #16
sli     v2.2s, v2.2s, #16
sli     v3.2s, v3.2s, #16

mov     v0.d[1], v0.d[0]
mov     v1.d[1], v1.d[0]
mov     v2.d[1], v2.d[0]
mov     v3.d[1], v3.d[0]

st1     {v0.8h-v3.8h}, [output]
Другие вопросы по тегам