Как трактовать результат vaddv_u8 в arm64 как неоновый регистр - PullRequest
0 голосов
/ 25 апреля 2018

vaddv_u8 и некоторые другие подобные новые v-intrinsics от AArch64 (arm64) возвращают uint8_t.Как можно обработать результат этой встроенной функции как неоновый регистр вместо обычного типа C?

Например :

void paddClz(uint8_t* x)
{
    uint8x8_t ret = vdup_n_u8(0);
    for (int i = 0; i < 8; ++i, x += 8)
    {
        uint8x8_t x8 = vld1_u8(x);
        uint8_t sum = vaddv_u8(x8);
        uint8x8_t r = vdup_n_u8(sum); //or: r = vset_lane_u8(sum, r, 0);
        r = vclz_u8(r);
        ret = vext_u8(ret, r, 1);
    }
    vst1_u8(x, ret);
}

, что сгенерировал clang:

paddClz(unsigned char*): // @paddClz(unsigned char*)
  mov x8, xzr
  movi d0, #0000000000000000
.LBB0_1: // =>This Inner Loop Header: Depth=1
  ldr d1, [x0, x8]
  add x8, x8, #8 // =8
  cmp w8, #64 // =64
  addv b1, v1.8b
  dup v1.8b, v1.b[0]   <<== useless! I only need/use/care about v1.b[0]
  clz v1.8b, v1.8b
  ext v0.8b, v0.8b, v1.8b, #1
  b.ne .LBB0_1
  str d0, [x0, #64]
  ret

Как видите, существует бесполезная внутренняя сущность dup, необходимая для преобразования результата uint8_t vaddv_u8 в тип, который будет работать в качестве аргумента для vclz_u8.Я выбираю только первую полосу из последующего vclz_u8 результата, поэтому дублирование ее на все дорожки будет напрасной тратой времени.

Как я могу записать ее по внутренним признакам, чтобы получить sum в переменной с неоновым типом, не делаякомпилятор выдает бесполезные коды операций?(И желательно без этого лишнего шума в исходном коде.) Чтобы было ясно и очевидно, если это не так: я не прошу оптимизировать или улучшить тот фрагмент кода, который я разместил;Я просто написал это, чтобы показать проблему.

Ответы [ 3 ]

0 голосов
/ 27 апреля 2018

Ваш обходной путь потенциально ухудшает производительность.Ваша проблема написана так, как будто вы хотите получить скалярный результат от вашего единственного вектора uint8_t.Нет ничего плохого в том, что инструкция vaddv_u8 возвращает скалярное значение.В ARMv8 «устройство NEON» теперь полностью интегрировано и не имеет большого штрафа за перемещение данных между регистрами NEON и ARM.Просто используйте встроенную букву C для подсчета начальных нулей результата, и вы получите то, что вам нужно:

int paddClz(const uint8_t* x)
{
    uint8x8_t x8 = vld1_u8(x);
    uint8_t sum = vaddv_u8(x8);
    return __builtin_clz(sum) - 24;
}

Интрисиал будет скомпилирован в одну инструкцию ARM (CLZ).

Если вы работаете с большим набором данных, напишите код на C, чтобы должным образом отразить этот факт.

0 голосов
/ 27 апреля 2018

Вы действительно должны получить тестовое устройство с SoC в порядке.Чипы Apple серии A вышли из строя, безусловно, самые мощные, если быть точными.

Ваша реализация может работать на вашем iPhone достаточно быстро, но будет чуть быстрее, чем самые простые версии C наядра по порядку, непригодные для использования.

Подумайте дважды, прежде чем приступить к написанию циклов на NEON.Вы можете избежать так называемых «горизонтальных» операций в большинстве случаев, транспонируя матрицу, а затем выполнить «вертикальную» математику.


#define vuzp8(a, b, c) ({ \
    c = vuzp_u8(a, b); \
    a = c.val[0]; \
    b = c.val[1]; \
})

void foo(uint8_t *pDst, uint8_t *pSrc)
{
    uint8x8x4_t top, bottom;
    uint8x8x2_t temp;

    top = vld4_u8(pSrc);
    pSrc += 32;
    bottom = vld4_u8(pSrc);

    vuzp8(top.val[0], bottom.val[0], temp);
    vuzp8(top.val[1], bottom.val[1], temp);
    vuzp8(top.val[2], bottom.val[2], temp);
    vuzp8(top.val[3], bottom.val[3], temp);

    top.val[0] += bottom.val[0];
    top.val[1] += bottom.val[1];
    top.val[2] += bottom.val[2];
    top.val[3] += bottom.val[3];

    top.val[0] += top.val[1];
    top.val[2] += top.val[3];

    top.val[0] += top.val[2];

    top.val[0] = vclz_u8(top.val[0]);

    vst1_u8(pDst, top.val[0]);
}

Другой пример, когда вы спрашиваетесебя, если intrinsux имеет смысл вообще.Его неуклюжесть делает код намного более сложным, и он недостаточно выразителен, чтобы сделать три 128-битных плюс одно 64-битное добавление вместо шести 64-битных.

Кроме того, вы должны дважды проверить, не компилятор не сделалопять все испортить, особенно когда вы делаете перестановки (vzip, vuzp, vtrn)

Я думаю, что машинный код будет в порядке на aarch32, но я не уверен в aarch64, где инструкции по перестановкеочень разные.

Я думаю, что вы уже поняли, почему я ненавижу intrinsux как вредитель.Это больше неприятностей, чем любая помощь.

PS: Планшет Android Teclast P10 является хорошим кандидатом в качестве тестового устройства aarch64: все восемь ядер одинаковы, Android 7.12 64bitустановлен, и стоит всего около $ 100.

0 голосов
/ 26 апреля 2018

Кажется, что Я могу сделать это в Clang :

int paddClz(const uint8_t* x)
{
    uint8x8_t x8 = vld1_u8(x);
    uint8_t sum = vaddv_u8(x8);
    uint8x8_t r;
    r = vset_lane_u8(sum, r, 0);
    r = vclz_u8(r);
    return vget_lane_u8(r, 0);
}

Это производит именно то, что я хочу:

addv b0, v0.8b
clz v0.8b, v0.8b

Однако, GCCпроизводит некоторый беспорядок из этого кода.Другая проблема заключается в том, что он использует неинициализированный r и в зависимости от того, как вы настроили сборку, он может быть неприемлемым.Более того, это не работает в более сложных сценариях.Есть ли лучший / правильный способ сделать это?

...