可能是一个过于具体的答案,但在支持 NEON 的 ARM 平台上,NEON 矢量化可用于使跨步复制更快。在资源相对有限的环境中,这可能会挽救生命,这可能是首先在该环境中使用 ARM 的原因。一个突出的例子是 Android,其中大多数设备仍然使用支持 NEON 的 ARM v7a 架构。
以下示例演示了这一点,将 YUV420sp 图像的半平面 UV 平面复制到 YUV420p 图像的平面 UV 平面是一个循环。源缓冲区和目标缓冲区的大小都是640*480/2
字节。所有示例均使用 Android NDK r9d 中的 g++ 4.8 编译。它们在三星 Exynos Octa 5420 处理器上执行:
级别 1:常规
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
仅编译,-O3
平均大约需要 1.5 毫秒。
第 2 级:使用移动指针展开和挤压更多
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
unsigned char* endptr = dstptr + stride;
while(dstptr<endptr){
*(dstptr + 0) = *(srcptr + 0);
*(dstptr + stride + 0) = *(srcptr + 1);
*(dstptr + 1) = *(srcptr + 2);
*(dstptr + stride + 1) = *(srcptr + 3);
*(dstptr + 2) = *(srcptr + 4);
*(dstptr + stride + 2) = *(srcptr + 5);
*(dstptr + 3) = *(srcptr + 6);
*(dstptr + stride + 3) = *(srcptr + 7);
*(dstptr + 4) = *(srcptr + 8);
*(dstptr + stride + 4) = *(srcptr + 9);
*(dstptr + 5) = *(srcptr + 10);
*(dstptr + stride + 5) = *(srcptr + 11);
*(dstptr + 6) = *(srcptr + 12);
*(dstptr + stride + 6) = *(srcptr + 13);
*(dstptr + 7) = *(srcptr + 14);
*(dstptr + stride + 7) = *(srcptr + 15);
srcptr+=16;
dstptr+=8;
}
}
仅编译,-O3
平均需要大约 1.15 毫秒。根据另一个答案,这可能与常规架构上的速度一样快。
第 3 级: Regular + GCC 自动 NEON 矢量化
void convertUVsp2UVp(
unsigned char* __restrict srcptr,
unsigned char* __restrict dstptr,
int stride)
{
for(int i=0;i<stride;i++){
dstptr[i] = srcptr[i*2];
dstptr[i + stride] = srcptr[i*2 + 1];
}
}
用 编译-O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp
,平均大约需要 0.6 毫秒。作为参考,一个字节,或者这里测试memcpy
的640*480
两倍,平均需要大约 0.6 毫秒。
附带说明一下,使用上面的 NEON 参数编译的第二个代码(展开和指针)需要大约相同的时间,0.6 毫秒。