使用 ARM NEON 指令查找数组的最小值和最大值

Posted

技术标签:

【中文标题】使用 ARM NEON 指令查找数组的最小值和最大值【英文标题】:Find minimum and maximum value of an array using ARM NEON instructions 【发布时间】:2014-10-28 14:36:34 【问题描述】:

我有以下代码,我想使用 ARM NEON 指令对其进行优化。我该如何实施? 感谢您的回答

unsigned char someVector[] = 1, 2, 4, 1, 2, 0, 8, 100;
unsigned char maxVal = 0, minVal = 255;
for (int i = 0; i < sizeof(someVector); i++)

    if (someVector[i] < minVal)
    
        minVal = someVector[i];
    
    else if (someVector[i] > maxVal)
    
        maxVal = someVector[i];
    

【问题讨论】:

您可以在 infocenter.arm.com 下载组装手册。然后你可以看看指令''vmin''和''vmax'' 感谢您的评论。我知道 vmin 和 vmax,但它们比较两个寄存器中的每个通道。然后我会成对地得到 8 个值的最小值和最大值,但不是所有值 三个连续的 vpmin 和 vpmax 将完成其余的工作。但是,最好使用 ARM 整数内核执行此操作,因为指令延迟会严重削弱性能。 NEON 最初并不打算处理如此小的数据。 【参考方案1】:

下面是一个高度优化的示例,如何在大型数组中查找最小值和最大值。如果 size 小于 128,该函数会简单地返回:

/*
 * minmax.S
 *
 *  Created on: 2014. 10. 29.
 *      Author: Jake Lee
 */


// unsigned int minmax(unsigned char *pSrc, unsigned int size);

    .text
    .arm
    .global minmax

    pSrc    .req    r0
    size    .req    r1

    qmin1   .req    q0
        dmina   .req    d0
        dminb   .req    d1

    qmax1   .req    q1
        dmaxa   .req    d2
        dmaxb   .req    d3

    qmin2   .req    q2
    qmax2   .req    q3

    .align 5
    .func
minmax:
    subs    size, size, #128
    bxmi    lr
    vmov.i8     qmin1, #0xff
    vmov.i8     qmax1, #0
    vmov.i8     qmin2, #0xff
    vmov.i8     qmax2, #0

    .align 5
1:
    vld1.8      q8, q9, [pSrc]!
    vld1.8      q10, q11, [pSrc]!
    vld1.8      q12, q13, [pSrc]!
    vld1.8      q14, q15, [pSrc]!
    subs    size, size, #128
    pld     [pSrc, #64*3]
    pld     [pSrc, #64*4]
    vmin.u8     qmin1, q8
    vmax.u8     qmax1, q8
    vmin.u8     qmin2, q9
    vmax.u8     qmax2, q9
    vmin.u8     qmin1, q10
    vmax.u8     qmax1, q10
    vmin.u8     qmin2, q11
    vmax.u8     qmax2, q11
    vmin.u8     qmin1, q12
    vmax.u8     qmax1, q12
    vmin.u8     qmin2, q13
    vmax.u8     qmax2, q13
    vmin.u8     qmin1, q14
    vmax.u8     qmax1, q14
    vmin.u8     qmin2, q15
    vmax.u8     qmax2, q15
    bpl     1b

// deal width residuals (size % 128)
    cmp     size, #-128
    addgt   pSrc, pSrc, size
    bgt     1b

// shrink to sixteen
    vmin.u8     qmin1, qmin2
    vmax.u8     qmax1, qmax2
// shrink to eight
    vpmin.u8    dmina, dmina, dminb
    vpmax.u8    dmaxa, dmaxa, dmaxb
// shrink to four
    vpmin.u8    dmina, dmina, dminb
    vpmax.u8    dmaxa, dmaxa, dmaxb
// shrink to two
    vpmin.u8    dmina, dmina, dminb
    vpmax.u8    dmaxa, dmaxa, dmaxb
// shrink to one
    vpmin.u8    dmina, dmina, dminb
    vpmax.u8    dmaxa, dmaxa, dmaxb

    vmov    r0, dmina[0]
    vmov    r1, dmaxa[0]

    and     r0, r0, #0xff
    and     r1, r1, #0xff
    orr     r0, r0, r1, lsl #16
    bx      lr
    .endfunc
    .end

返回值是一个无符号整数。低 16 位包含 min 和高位 max:

result = minmax(pSrc, size);
min = result & 0xff;
max = result >> 16;

【讨论】:

谢谢杰克。实际上我的数组是图像, someVector 只是一个例子,很抱歉造成混乱。所以阵列明显更长。对于一个图像,你会在 8 个像素的基础上调用 vmin 和 vmax,最后调用 vpmin 和 vpmax 的三倍吗? 我的代码一次执行 16 个字节。 (q 个寄存器)并且每次迭代执行 128 个字节。看来您正试图让它与内在函数一起使用,但这甚至不会比上面的程序集版本快一半。 @Jake'Alquimista'LEE 为什么你的plds是+64*3和+64*4? “pSrc”不是要加载的下一个地址,为什么不预加载 pSrc 本身呢?我可以将 pld 连续用于 2 个不同的寄存器以使其更快吗?只是没有任何有用的文档..【参考方案2】:

GCC 将自动对其进行矢量化,只需进行少量修改。

unsigned char someVector[256] =  1, 2, 4, 1, 2, 0, 8, 100 ;
unsigned char maxVal = 0, minVal = 255;

void f(void)

    unsigned char mn = 255, mx = 0;
    for (int i = 0; i < sizeof(someVector); i++) 
        if (someVector[i] < mn) 
            mn = someVector[i];
        
        if (someVector[i] > mx) 
            mx = someVector[i];
        
    
    maxVal = mx;
    minVal = mn;

编译

$ arm-unknown-linux-gnueabihf-gcc -O3 -std=c11 -mfpu=neon -c test.c

$ arm-unknown-linux-gnueabihf-gcc -O2 -ftree-vectorize -std=c11 -mfpu=neon -c test.c

如果您编写 NEON 内部函数或汇编程序,您可以做得比 GCC 更好。

【讨论】:

感谢 Charles,很遗憾我正在使用 WinCE,而不是 gcc。 @Zoli 认为自己很幸运:MS 的 ARM 编译器比 GCC 好得多。

以上是关于使用 ARM NEON 指令查找数组的最小值和最大值的主要内容,如果未能解决你的问题,请参考以下文章

Javascript:使用 reduce() 查找最小值和最大值?

在数组中查找最小值和最大值

查找数组的最小值和最大值

在给定数组中查找最小值和最大值,其中约束应最小值索引应小于其最大值

c_cpp 使用分而治之的方法查找未排序数组中的最小值和最大值

ARM NEON 中的指令调度