为什么 ARM NEON 不比普通 C++ 快? [英] Why ARM NEON not faster than plain C++?
问题描述
这是一个 C++ 代码:
Here is a C++ code:
#define ARR_SIZE_TEST ( 8 * 1024 * 1024 )
void cpp_tst_add( unsigned* x, unsigned* y )
{
for ( register int i = 0; i < ARR_SIZE_TEST; ++i )
{
x[ i ] = x[ i ] + y[ i ];
}
}
这是一个霓虹灯版本:
void neon_assm_tst_add( unsigned* x, unsigned* y )
{
register unsigned i = ARR_SIZE_TEST >> 2;
__asm__ __volatile__
(
".loop1: \n\t"
"vld1.32 {q0}, [%[x]] \n\t"
"vld1.32 {q1}, [%[y]]! \n\t"
"vadd.i32 q0 ,q0, q1 \n\t"
"vst1.32 {q0}, [%[x]]! \n\t"
"subs %[i], %[i], $1 \n\t"
"bne .loop1 \n\t"
: [x]"+r"(x), [y]"+r"(y), [i]"+r"(i)
:
: "memory"
);
}
测试功能:
void bench_simple_types_test( )
{
unsigned* a = new unsigned [ ARR_SIZE_TEST ];
unsigned* b = new unsigned [ ARR_SIZE_TEST ];
neon_tst_add( a, b );
neon_assm_tst_add( a, b );
}
我已经测试了这两种变体,这里有一份报告:
I have tested both variants and here are a report:
add, unsigned, C++ : 176 ms
add, unsigned, neon asm : 185 ms // SLOW!!!
我还测试了其他类型:
add, float, C++ : 571 ms
add, float, neon asm : 184 ms // FASTER X3!
问题:为什么 32 位整数类型的 neon 速度较慢?
THE QUESTION: Why neon is slower with 32-bit integer types?
我为 Android NDK 使用了最新版本的 GCC.NEON 优化标志已打开.这是一个反汇编的 C++ 版本:
I used last version of GCC for Android NDK. NEON optimization flags were turned on. Here is a disassembled C++ version:
MOVS R3, #0
PUSH {R4}
loc_8
LDR R4, [R0,R3]
LDR R2, [R1,R3]
ADDS R2, R4, R2
STR R2, [R0,R3]
ADDS R3, #4
CMP.W R3, #0x2000000
BNE loc_8
POP {R4}
BX LR
这是霓虹灯的拆解版:
MOV.W R3, #0x200000
.loop1
VLD1.32 {D0-D1}, [R0]
VLD1.32 {D2-D3}, [R1]!
VADD.I32 Q0, Q0, Q1
VST1.32 {D0-D1}, [R0]!
SUBS R3, #1
BNE .loop1
BX LR
这是所有的基准测试:
add, char, C++ : 83 ms
add, char, neon asm : 46 ms FASTER x2
add, short, C++ : 114 ms
add, short, neon asm : 92 ms FASTER x1.25
add, unsigned, C++ : 176 ms
add, unsigned, neon asm : 184 ms SLOWER!!!
add, float, C++ : 571 ms
add, float, neon asm : 184 ms FASTER x3
add, double, C++ : 533 ms
add, double, neon asm : 420 ms FASTER x1.25
问题:为什么 32 位整数类型的 neon 速度较慢?
THE QUESTION: Why neon is slower with 32-bit integer types?
推荐答案
Cortex-A8 上的 NEON 管道是按顺序执行的,并且具有有限的命中率(无重命名),因此您受到内存的限制延迟(因为您使用的缓存大小超过 L1/L2 缓存).您的代码直接依赖于从内存加载的值,因此它会不断等待内存.这可以解释为什么 NEON 代码比非 NEON 代码稍慢(少量).
The NEON pipeline on Cortex-A8 is in-order executing, and has limited hit-under-miss (no renaming), so you're limited by memory latency (as you're using more than L1/L2 cache size). Your code has immediate dependencies on the values loaded from memory, so it'll stall constantly waiting for memory. This would explain why the NEON code is slightly (by a tiny amount) slower than non-NEON.
您需要展开装配环并增加负载和使用之间的距离,例如:
You need to unroll the assembly loops and increase the distance between load and use, e.g:
vld1.32 {q0}, [%[x]]!
vld1.32 {q1}, [%[y]]!
vld1.32 {q2}, [%[x]]!
vld1.32 {q3}, [%[y]]!
vadd.i32 q0 ,q0, q1
vadd.i32 q2 ,q2, q3
...
有很多霓虹灯寄存器,所以你可以展开很多.整数代码会遇到同样的问题,程度较小,因为 A8 整数具有更好的命中率而不是停顿.与 L1/L2 缓存相比,基准测试的瓶颈将是内存带宽/延迟.您可能还希望以较小的大小 (4KB..256KB) 运行基准测试,以查看数据完全缓存在 L1 和/或 L2 中时的效果.
There's plenty of neon registers so you can unroll it a lot. Integer code will suffer the same issue, to a lesser extent because A8 integer has better hit-under-miss instead of stalling. The bottleneck is going to be memory bandwidth/latency for benchmarks so large compared to L1/L2 cache. You might also want to run the benchmark at smaller sizes (4KB..256KB) to see effects when data is cached entirely in L1 and/or L2.
这篇关于为什么 ARM NEON 不比普通 C++ 快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!