使用 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() 查找最小值和最大值?
在给定数组中查找最小值和最大值,其中约束应最小值索引应小于其最大值