ARM NEON:将二进制每像素 8 位图像(仅 0/1)转换为每像素 1 位?

Kat*_*Lee 2 arm neon

我正在执行一项任务,将大型二进制标签图像(uint8_t每个像素有 8 位 ( ),每个像素只能是 0 或 1(或 255))转换为uint64_t数字数组,数字中的每个位uint64_t代表一个标签像素。

\n

例如,

\n

输入数组:0 1 1 0 ... (00000000 00000001 00000001 00000000 ...)

\n

或输入数组:0 255 255 0 ... (00000000 11111111 11111111 00000000 ...)

\n

输出数组(数字):(6因为将每个转换uint8_t为位后,它变成了0110

\n

目前实现这一点的C代码是:

\n
 for (int j = 0; j < width >> 6; j++) {\n        uint8_t* in_ptr= in + (j << 6);\n        uint64_t out_bits = 0;\n        if (in_ptr[0]) out_bits |= 0x0000000000000001;\n        if (in_ptr[1]) out_bits |= 0x0000000000000002;\n        .\n        .\n        .\n        if (in_ptr[63]) out_bits |= 0x8000000000000000;\n       *output = obits\xef\xbc\x9b output ++;\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

ARM NEON 可以优化此功能吗?请帮忙。谢谢你!

\n

Jak*_*LEE 5

假设输入值为 0 或 255,下面是相当简单的基本版本,特别是对于具有 Intel SSE/AVX 经验的人来说。

void foo_basic(uint8_t *pDst, uint8_t *pSrc, intptr_t length)
{
    //assert(length >= 64);
    //assert(length & 7 == 0);
    uint8x16_t in0, in1, in2, in3;
    uint8x8_t out;
    const uint8x16_t mask = {1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};

    length -= 64;

    do {
        do {
            in0 = vld1q_u8(pSrc); pSrc += 16;
            in1 = vld1q_u8(pSrc); pSrc += 16;
            in2 = vld1q_u8(pSrc); pSrc += 16;
            in3 = vld1q_u8(pSrc); pSrc += 16;

            in0 &= mask;
            in1 &= mask;
            in2 &= mask;
            in3 &= mask;

            in0 = vpaddq_u8(in0, in1);
            in2 = vpaddq_u8(in2, in3);

            in0 = vpaddq_u8(in0, in2);

            out = vpadd_u8(vget_low_u8(in0), vget_high_u8(in0));

            vst1_u8(pDst, out); pDst += 8;

            length -= 64;
        } while (length >=0);

        pSrc += length>>3;
        pDst += length;
    } while (length > -64);
}
Run Code Online (Sandbox Code Playgroud)

然而,Neon 具有非常用户友好且高效的排列和位操作指令,允许“垂直”

void foo_advanced(uint8_t *pDst, uint8_t *pSrc, intptr_t length)
{
    //assert(length >= 128);
    //assert(length & 7 == 0);
    uint8x16x4_t in0, in1;
    uint8x16x2_t row04, row15, row26, row37;

    length -= 128;

    do {
        do {
            in0 = vld4q_u8(pSrc); pSrc += 64;
            in1 = vld4q_u8(pSrc); pSrc += 64;

            row04 = vuzpq_u8(in0.val[0], in1.val[0]);
            row15 = vuzpq_u8(in0.val[1], in1.val[1]);
            row26 = vuzpq_u8(in0.val[2], in1.val[2]);
            row37 = vuzpq_u8(in0.val[3], in1.val[3]);

            row04.val[0] = vsliq_n_u8(row04.val[0], row15.val[0], 1);
            row26.val[0] = vsliq_n_u8(row26.val[0], row37.val[0], 1);
            row04.val[1] = vsliq_n_u8(row04.val[1], row15.val[1], 1);
            row26.val[1] = vsliq_n_u8(row26.val[1], row37.val[1], 1);

            row04.val[0] = vsliq_n_u8(row04.val[0], row26.val[0], 2);
            row04.val[1] = vsliq_n_u8(row04.val[1], row26.val[1], 2);

            row04.val[0] = vsliq_n_u8(row04.val[0], row04.val[1], 4);

            vst1q_u8(pDst, row04.val[0]); pDst += 16;

            length -= 128;
        } while (length >=0);

        pSrc += length>>3;
        pDst += length;
    } while (length > -128);
}
Run Code Online (Sandbox Code Playgroud)

仅 Neon 的高级版本更短、更快,但GCC在处理 Neon 特定排列指令(例如vtrnvzip和 )方面非常糟糕vuzp

https://godbolt.org/z/bGdbohqKe

Clang也好不到哪里去:它会发送不必要的垃圾邮件,vorr而.GCCvmov

    .syntax unified
    .arm
    .arch   armv7-a
    .fpu    neon
    .global foo_asm
    .text

.func
.balign 64
foo_asm:
    sub     r2, r2, #128

.balign 16
1:
    vld4.8      {d16, d18, d20, d22}, [r1]!
    vld4.8      {d17, d19, d21, d23}, [r1]!
    vld4.8      {d24, d26, d28, d30}, [r1]!
    vld4.8      {d25, d27, d29, d31}, [r1]!
    subs    r2, r2, #128

    vuzp.8      q8, q12
    vuzp.8      q9, q13
    vuzp.8      q10, q14
    vuzp.8      q11, q15

    vsli.8      q8, q9, #1
    vsli.8      q10, q11, #1
    vsli.8      q12, q13, #1
    vsli.8      q14, q15, #1

    vsli.8      q8, q10, #2
    vsli.8      q12, q14, #2

    vsli.8      q8, q12, #4

    vst1.8      {q8}, [r0]!
    bpl     1b

    add     r1, r1, r2
    cmp     r2, #-128
    add     r0, r0, r2, asr #3

    bgt     1b
.balign 8
    bx      lr

.endfunc
.end
Run Code Online (Sandbox Code Playgroud)

最内层循环包括:
GCC:32 条指令
Clang:30 条指令
Asm:18 条指令

不需要火箭科学就能找出哪一个最快以及快多少:如果你要做排列,永远不要相信编译器。