Sma*_*lti 29 c++ arm neon cortex-a8
这是一个C++代码:
#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 ];
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个霓虹灯版本:
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"
);
}
Run Code Online (Sandbox Code Playgroud)
测试功能:
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 );
}
Run Code Online (Sandbox Code Playgroud)
我测试了两种变体,这是一份报告:
add, unsigned, C++ : 176 ms
add, unsigned, neon asm : 185 ms // SLOW!!!
Run Code Online (Sandbox Code Playgroud)
我还测试了其他类型:
add, float, C++ : 571 ms
add, float, neon asm : 184 ms // FASTER X3!
Run Code Online (Sandbox Code Playgroud)
问题:为什么使用32位整数类型的霓虹灯会变慢?
我使用了最新版本的GCC for Android NDK.打开了NEON优化标志.这是一个反汇编的C++版本:
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
Run Code Online (Sandbox Code Playgroud)
这是霓虹灯的拆解版本:
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
Run Code Online (Sandbox Code Playgroud)
以下是所有基准测试:
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
Run Code Online (Sandbox Code Playgroud)
问题:为什么使用32位整数类型的霓虹灯会变慢?
Joh*_*ley 44
Cortex-A8上的NEON管道是按顺序执行的,并且具有有限的命中未命中(无重命名),因此您受到内存延迟的限制(因为您使用的不仅仅是L1/L2缓存大小).你的代码直接依赖于从内存加载的值,所以它会不停地等待内存.这可以解释为什么NEON代码比非NEON稍微(少量)慢.
您需要展开装配循环并增加装载和使用之间的距离,例如:
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
...
Run Code Online (Sandbox Code Playgroud)
有很多霓虹灯寄存器,所以你可以打开很多.整数代码将遇到相同的问题,在较小程度上,因为A8整数具有更好的命中未命中而不是停止.瓶颈将是与L1/L2缓存相比如此大的基准测试的内存带宽/延迟.您可能还希望以较小的大小(4KB..256KB)运行基准测试,以查看数据完全缓存在L1和/或L2中时的效果.
Exo*_*ase 17
虽然在这种情况下你受到主内存延迟的限制,但NEON版本的速度并不比ASM版本慢.
在这里使用循环计算器:
http://pulsar.webshaker.net/ccc/result.php?lng=en
您的代码应该在缓存未命中处罚之前需要7个周期.它比您预期的要慢,因为您使用的是未对齐的加载,以及添加和存储之间的延迟.
同时,编译器生成的循环需要6个周期(它通常也没有很好地调度或优化).但它正在做四分之一的工作.
脚本中的循环计数可能不完美,但我没有看到任何看起来明显错误的东西,所以我认为它们至少是接近的.如果最大限度地获取带宽(如果循环不是64位对齐),则有可能在分支上进行额外的循环,但在这种情况下,有很多停顿可以隐藏它.
答案不是Cortex-A8上的整数有更多隐藏延迟的机会.实际上,由于NEON交错的管道和发布队列,它通常较少.当然,这只适用于Cortex-A8 - 在Cortex-A9上情况可能会逆转(NEON按顺序调度并与整数并行调度,而整数具有无序功能).因为你标记了这个Cortex-A8,我假设你正在使用它.
这需要更多的调查.以下是为什么会发生这种情况的一些想法:
你问过NEON在这种情况下有什么好处 - 实际上,NEON特别适合你在内存中流式传输的情况.诀窍是你需要使用预加载以尽可能地隐藏主内存延迟.预加载将提前将内存存入L2(非L1)缓存.这里NEON比整数有一个很大的优势,因为它可以隐藏很多L2缓存延迟,因为它有交错的管道和问题队列,但也因为它有直接路径.我希望你看到有效的L2延迟低至0-6个循环,如果你有较少的依赖关系并且不会耗尽加载队列,那么你会看到有效的L2延迟,而在整数上你可能会陷入一个你无法避免的好的~16个循环(可能取决于Cortex-A8虽然).
因此,我建议您将数组与高速缓存行大小(64字节)对齐,展开循环以一次至少执行一个高速缓存行,使用对齐的加载/存储(放置:地址后的128)并添加用于加载多个缓存行的pld指令.至于有多少条线路:从小处开始,继续增加它直到你不再看到任何好处.
Jak*_*LEE 12
您的C++代码也没有优化.
#define ARR_SIZE_TEST ( 8 * 1024 * 1024 )
void cpp_tst_add( unsigned* x, unsigned* y )
{
unsigned int i = ARR_SIZE_TEST;
do
{
*x++ += *y++;
} (while --i);
}
Run Code Online (Sandbox Code Playgroud)
这个版本消耗2个循环/迭代.
此外,您的基准测试结果并不让我感到惊讶.
32位:
这个功能对于NEON来说太简单了.没有足够的算术运算留下任何优化空间.
是的,它非常简单,C++和NEON版本几乎每次都受到管道危害的影响而没有任何真正的机会从双重问题能力中受益.
虽然NEON版本可能会同时处理4个整数,但它也会受到各种危害的影响.就这样.
8位:
ARM从内存中读取每个字节非常慢.这意味着,虽然NEON显示出与32位相同的特性,但ARM仍然严重滞后.
16位:这里也一样.除了ARM的16位读取并不是那么糟糕.
float:C++版本将编译为VFP代码.Coretex A8上没有完整的VFP,但VFP lite并没有管理任何糟糕的东西.
这并不是说NEON表现得很奇怪,处理32位.它只是满足理想条件的ARM.由于其简单性,您的功能非常不适合基准测试目的.尝试更复杂的东西,如YUV-RGB转换:
仅供参考,我完全优化的NEON版本的运行速度大约是完全优化的C版本的20倍,是我完全优化的ARM装配版本的8倍.我希望能让你知道NEON有多强大.
最后但并非最不重要的是,ARM指令PLD是NEON最好的朋友.放置得当,它将带来至少40%的性能提升.
您可以尝试一些修改来改进代码.
如果可以: - 使用第三个缓冲区来存储结果. - 尝试在8个字节上对齐数据.
代码应该是这样的(抱歉,我不知道gcc内联语法)
.loop1:
vld1.32 {q0}, [%[x]:128]!
vld1.32 {q1}, [%[y]:128]!
vadd.i32 q0 ,q0, q1
vst1.32 {q0}, [%[z]:128]!
subs %[i], %[i], $1
bne .loop1
Run Code Online (Sandbox Code Playgroud)
正如Exophase所说,你有一些管道延迟.也许你可以试试
vld1.32 {q0}, [%[x]:128]
vld1.32 {q1}, [%[y]:128]!
sub %[i], %[i], $1
.loop1:
vadd.i32 q2 ,q0, q1
vld1.32 {q0}, [%[x]:128]
vld1.32 {q1}, [%[y]:128]!
vst1.32 {q2}, [%[z]:128]!
subs %[i], %[i], $1
bne .loop1
vadd.i32 q2 ,q0, q1
vst1.32 {q2}, [%[z]:128]!
Run Code Online (Sandbox Code Playgroud)
最后,显然你会使内存带宽饱和
您可以尝试添加一个小的
PLD [%[x], 192]
Run Code Online (Sandbox Code Playgroud)
进入你的循环.
告诉我们它是否更好......