为什么AArch64没有LDM和STM指令

本文

主要介绍为什么AArch64没有LDM和STM指令了,而是用LDP跟STP。

版本 说明
0.1 初版发布

写在前头

AArch32 指令集中包含多数据传输指令LDM、STM,也就是单条指令可以传输多个寄存器的值与内存交互,这对于发生异常时将当前寄存器压入栈很有用,通过单条指令STM可以实现此操作,出栈时同样使用LDM即可。但是到了AArch64里面就取消了多数据传输指令LDM、STM,而是使用LDP和STP,固定的一次最多只能取2个值。原因如何呢?请看下文。

关于CPU内部微操作的拆分

对于复杂功能指令的实现,出于指令功能复杂以及读取和写回寄存器端口资源的限制,CPU内部会对指令进行微操作拆分。比如CPU内部流水线中单拍最多读取3个64bit寄存器和写回两个64bit寄存器,对于LDM和STM这种多数据传输操作,实际在CPU执行过程中会进行微操作的拆分。换句话说,LDM和STM只不过是对多个LD和ST的打包,在CPU里还是分多条微操作执行,LDM和STM指令除了方便软件工程师使用,而对CPU并没有提高执行效率。既然可以方便软件工程师使用,毕竟还是有利的,那为什么在AArch64还是取消了该指令呢?继续看下文。

CPU对LD和ST的操作

当前CPU的LD和ST,大多分为两个独立的流水线,也就是说配合多发射就可能在一个cycle内同时执行两条(一个ldr、一个str)指令。这里如果执行一条LDM,会使LD Pipe处于持续工作状态,而ST Pipe是空闲状态,执行一条STM情况类似。所以为了最大化利用LD和ST独立流水线的特性,在软件使用层面上,应尽量减少这种突发的多LD和多ST事件,而使用间隔的LD和ST可以利用双pipeline实现该两条指令并发执行,提高执行效率。所以,也就没有LDM和STM指令的必要性了。

LDP/STP

  • 32bit模式下也是有LDRD跟STD指令的,但是在64bit模式下任意的两个整型寄存器都是可以读或者写的
  • 数据的读写是内存里面的连续位置的
  • 地址模式更严格:只能base + offset的模式进行访存,offset范围是正负127范围(7bit)
  • 与32bit的LDRD跟STD指令不同的是:64bit模式下支持非对齐访问
  • LDP是支持SIMD
  • 详细信息请看ARM手册。

LDNP/STNP

  • LDNP跟STNP是Armv8才有的机制,non-temporal代表读取的数据是不会存保存到cache。比如我只要2byte的数据,通常系统会加载1个cache line进来,但是使用LDNP指令,系统不会加载整个cache line进了,而是只加载2byte,从而减少总线流量,进而可能减少取数据的时间。因此LDNP这种指令只适合读取数据量少且不常访问的情况。
  • non-temporal加载和存储能够放松对访存顺序的要求,LDNP可能比LDR要提前执行(因为LDNP不需要进行cache line fill操作,而是流式读),但是这样顺序错了,因此要加barriar来保证执行顺序。(如果两个内存读之间存在地址依赖关系,第二个读指令是LDNP产生的,然后在没有任何其他屏障机制来保证顺序的情况下,这个可能就会乱序执行的!)
1
2
3
4
5
6
7
8
//错误的用法
LDR x0, [x3]
LDNP x2, x1, [x0]

//正确用法
LDR x0, [x3]
DSB nshld
LDNP x2, x1, [x0]
  • 简单理解就是:LDNP指令会告诉memory子系统,我是流式访存,这个数据我暂时只用一次,因此不用读取至cache。

总结

  • 在Arm v8-A架构里面因为有两个分别独立的ld、st pipeline,因此交叉使用LDP、STP指令可以并行使用pipeline,增大吞吐(因为armv7一般是单个加载存储单元,因此LDM、STM更有效)
  • LDNP、STNP是流式处理数据的,不会进行cache line fill操作,因此适合少量数据处理的情形,减少总线流量,比如堆栈恢复的时候,堆栈就几个字节的数据,常规操作是要加载一个cache line,不仅占用cahche空间,还增大总线流量,因此用LDNP更有效,不过在与LDR存在依赖时,要使用数据隔离。

文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。