ARM ARM 实际上并没有在该指令的正确使用方式上提供太多信息,但我发现它在其他地方使用以知道它需要一个地址作为在何处读取下一个值的提示。
我的问题是,给定一个 256 字节的紧密复制ldm/stm
指令循环,比如 r4-r11 x 8,最好在复制之前预取每个缓存行,在每个指令对之间,或者根本不这样memcpy
做问题不是同时读取和写入内存的同一区域。很确定我的缓存行大小是 64 字节,但它可能是 32 字节 - 在此处编写最终代码之前等待确认。
来自Cortex-A 系列程序员指南,第 17.4 章(注意:ARM11 的某些细节可能不同):
memcpy() 的最佳性能是使用整个高速缓存行的 LDM 实现的,然后使用整个高速缓存行的 STM 写入这些值。商店的对齐比负载的对齐更重要。应尽可能使用 PLD 指令。加载/存储单元中有四个 PLD 插槽。PLD 指令优先于自动预取器,并且在整数流水线性能方面没有成本。最佳 memcpy() 的 PLD 指令的确切时序在系统之间可能略有不同,但 PLD 到当前复制行前三个缓存行的地址是一个有用的起点。
在 Linux 内核中可以找到一个合理通用的复制循环示例,该循环利用缓存行大小的LDM
/STM
块和/或可用的地方, . 这实现了 Igor 上面提到的关于预加载的使用,并说明了阻塞。PLD
arch/arm/lib/copy_page.S
请注意,在 ARMv7(其中缓存线大小通常为 64 字节)上,不可能将LDM
完整的缓存线作为单个操作(只有 14 个 regs 可以使用,因为SP
/PC
不能用于此操作)。因此,您可能必须使用两/四对LDM
/ STM
。
要真正获得“最快”的 ARM asm 代码,您需要在系统上测试不同的方法。就 ldm/stm 循环而言,这个似乎最适合我:
// Use non-conflicting register r12 to avoid waiting for r6 in pld
pld [r6, #0]
add r12, r6, #32
1:
ldm r6!, {r0, r1, r2, r3, r4, r5, r8, r9}
pld [r12, #32]
stm r10!, {r0, r1, r2, r3, r4, r5, r8, r9}
subs r11, r11, #16
ldm r6!, {r0, r1, r2, r3, r4, r5, r8, r9}
pld [r12, #64]
stm r10!, {r0, r1, r2, r3, r4, r5, r8, r9}
add r12, r6, #32
bne 1b
上面的块假定您已经设置了 r6、r10、r11,并且此循环按 r11 字数而不是字节数倒计时。我已经在 Cortex-A9 (iPad2) 上对此进行了测试,并且在该处理器上似乎有相当不错的结果。但要小心,因为在 Cortex-A8 (iPhone4) 上,至少对于较大的副本来说,NEON 循环似乎比 ldm/stm 更快。