ARM架构是当今最广泛使用的处理器架构之一,从嵌入式设备到高性能服务器,ARM处理器几乎无处不在。理解ARM的寄存器组对于任何希望在底层编程或优化ARM系统的人来说都是至关重要的。
寄存器是处理器内部的高速存储单元,比内存快数百倍。ARM处理器的性能很大程度上依赖于其精心设计的寄存器组。ARM架构的寄存器组随着不同版本(如ARMv7、ARMv8)略有不同,但核心概念保持一致。以下我们主要以ARMv8-A架构为例进行讲解。
从理论上讲,寄存器访问速度可以通过以下公式量化:
T 访问 = 1 f C P U T_{访问} = \frac{1}{f_{CPU}} T访问=fCPU1
其中 f C P U f_{CPU} fCPU是CPU的时钟频率。相比之下,内存访问延迟通常是:
T 内存 = n f C P U T_{内存} = \frac{n}{f_{CPU}} T内存=fCPUn
其中 n n n通常在数十到数百个周期之间,这就解释了为什么寄存器访问比内存访问快几个数量级。
在ARM架构中,通用寄存器是最基础的寄存器类型。在32位ARM架构(如ARMv7)中,有16个主要的32位寄存器,标记为R0至R15。在64位ARM架构(ARMv8)中,这些扩展为31个64位通用寄存器,标记为X0至X30,同时每个64位寄存器的低32位可以作为W0至W30单独访问。
寄存器使用遵循以下数学公式表达的地址计算:
对于32位模式:
A d d r e s s = B a s e _ R e g i s t e r + O f f s e t Address = Base\_Register + Offset Address=Base_Register+Offset
其中 B a s e _ R e g i s t e r Base\_Register Base_Register通常是R0-R14中的一个, O f f s e t Offset Offset可以是立即数或其他寄存器的值。
对于64位模式:
A d d r e s s = X _ R e g i s t e r + ( O f f s e t × S c a l e ) + E x t e n d ( I n d e x _ R e g i s t e r ) × S c a l e Address = X\_Register + (Offset \times Scale) + Extend(Index\_Register) \times Scale Address=X_Register+(Offset×Scale)+Extend(Index_Register)×Scale
其中 X _ R e g i s t e r X\_Register X_Register通常是X0-X30中的一个, O f f s e t Offset Offset可以是立即数, I n d e x _ R e g i s t e r Index\_Register Index_Register是另一个寄存器, E x t e n d Extend Extend是符号或零扩展函数, S c a l e Scale Scale是缩放因子(可以是1、2、4或8)。
在ARMv8.3及以上版本中,引入了指针认证机制(PAC),通过以下函数保护指针:
P A C ( p t r , k e y , m o d i f i e r ) = ( p t r & ∼ m a s k ) ∣ ( h a s h ( p t r , k e y , m o d i f i e r ) & m a s k ) PAC(ptr, key, modifier) = (ptr \& \sim mask) | (hash(ptr, key, modifier) \& mask) PAC(ptr,key,modifier)=(ptr&∼mask)∣(hash(ptr,key,modifier)&mask)
其中 p t r ptr ptr是原始指针, k e y key key是密钥, m o d i f i e r modifier modifier是上下文信息, m a s k mask mask定义了用于存储认证码的位。
某些寄存器具有特殊用途,例如:
程序计数器(PC):在ARMv7中是R15,指向当前执行的指令地址。在ARMv8中,PC不再是通用寄存器的一部分,而是单独的寄存器。
在分支预测中,ARM使用动态分支预测器预测PC的下一个值,可以用马尔可夫模型表示:
P ( P C t + 1 = t a r g e t ∣ P C t = c u r r e n t ) = f ( h i s t o r y ) P(PC_{t+1} = target | PC_t = current) = f(history) P(PCt+1=target∣PCt=current)=f(history)
其中 f ( h i s t o r y ) f(history) f(history)是基于历史执行路径的预测函数。
链接寄存器(LR):在ARMv7中是R14,在ARMv8中是X30,存储子程序返回地址。当调用函数时,返回地址自动存储在LR中,函数返回时使用。数学上表示为:
L R = P C + 4 LR = PC + 4 LR=PC+4 (对于ARM指令集)
L R = P C + 2 LR = PC + 2 LR=PC+2 (对于Thumb指令集)
在递归调用中,LR需要保存在栈上,调用深度与栈使用量的关系为:
S t a c k _ U s a g e = ∑ i = 1 d e p t h f r a m e _ s i z e i Stack\_Usage = \sum_{i=1}^{depth} frame\_size_i Stack_Usage=i=1∑depthframe_sizei
其中 f r a m e _ s i z e i frame\_size_i frame_sizei是第 i i i层调用的栈帧大小。
栈指针(SP):在ARMv7中是R13,在ARMv8中是单独的SP寄存器,指向当前栈顶。栈操作可以表示为:
入栈操作: S P = S P − s i z e SP = SP - size SP=SP−size
出栈操作: S P = S P + s i z e SP = SP + size SP=SP+size
其中 s i z e size size是操作的总字节数,必须在AArch64中保持16字节对齐,可表示为:
S P m o d 16 = 0 SP \mod 16 = 0 SPmod16=0
ARMv8.1引入了BTI(分支目标识别)机制,通过特殊指令保护间接分支目标,防止ROP攻击:
V a l i d _ T a r g e t = ( I n s t r u c t i o n [ 31 : 0 ] = = B T I _ E n c o d i n g ) Valid\_Target = (Instruction[31:0] == BTI\_Encoding) Valid_Target=(Instruction[31:0]==BTI_Encoding)
ARM架构使用程序状态寄存器来存储处理器状态信息,包括条件标志、中断禁用标志、处理器模式等。
CPSR包含处理器的当前状态,其位域分配如下:
C P S R = { N , Z , C , V , Q , J , G E [ 3 : 0 ] , I T [ 7 : 0 ] , E , A , I , F , T , M [ 4 : 0 ] } CPSR = \{N, Z, C, V, Q, J, GE[3:0], IT[7:0], E, A, I, F, T, M[4:0]\} CPSR={N,Z,C,V,Q,J,GE[3:0],IT[7:0],E,A,I,F,T,M[4:0]}
其中:
条件执行的完整数学表达:
E Q : Z = 1 EQ: Z = 1 EQ:Z=1
N E : Z = 0 NE: Z = 0 NE:Z=0
C S / H S : C = 1 CS/HS: C = 1 CS/HS:C=1
C C / L O : C = 0 CC/LO: C = 0 CC/LO:C=0
M I : N = 1 MI: N = 1 MI:N=1
P L : N = 0 PL: N = 0 PL:N=0
V S : V = 1 VS: V = 1 VS:V=1
V C : V = 0 VC: V = 0 VC:V=0
H I : C = 1 ∧ Z = 0 HI: C = 1 \land Z = 0 HI:C=1∧Z=0
L S : C = 0 ∨ Z = 1 LS: C = 0 \lor Z = 1 LS:C=0∨Z=1
G E : N = V GE: N = V GE:N=V
L T : N ≠ V LT: N \neq V LT:N=V
G T : Z = 0 ∧ N = V GT: Z = 0 \land N = V GT:Z=0∧N=V
L E : Z = 1 ∨ N ≠ V LE: Z = 1 \lor N \neq V LE:Z=1∨N=V
A L : 不考虑条件标志(永远执行) AL: 不考虑条件标志(永远执行) AL:不考虑条件标志(永远执行)
在ARMv8的AArch64状态中,PSTATE替代了CPSR,引入了额外的位如UAO(用户访问覆盖)、PAN(特权访问从不)、SSBS(推测性存储旁路安全位)等,用于增强安全性和处理器功能。PSTATE的转换状态矩阵可表示为:
P ( P S T A T E t + 1 ∣ P S T A T E t , I n s t r u c t i o n t ) = { 1 , 如果 I n s t r u c t i o n t 导致 P S T A T E t 变为 P S T A T E t + 1 0 , 其他情况 P(PSTATE_{t+1} | PSTATE_t, Instruction_t) = \begin{cases} 1, & \text{如果$Instruction_t$导致$PSTATE_t$变为$PSTATE_{t+1}$} \\ 0, & \text{其他情况} \end{cases} P(PSTATEt+1∣PSTATEt,Instructiont)={1,0,如果Instructiont导致PSTATEt变为PSTATEt+1其他情况
当处理器模式改变时(如进入异常处理),CPSR的值会被保存到SPSR中,以便在返回时恢复。在ARMv8中,每个异常级别(EL0-EL3)都有自己的SPSR_ELx。
异常返回时的状态恢复可以表示为:
P S T A T E = S P S R _ E L x PSTATE = SPSR\_ELx PSTATE=SPSR_ELx
同时进行异常返回的指令权限检查:
A l l o w e d _ R e t u r n = ( E L _ t a r g e t ≤ E L _ c u r r e n t ) ∧ ( E L _ t a r g e t ≥ 1 ∨ ( E L _ t a r g e t = 0 ∧ ! i s _ s e c u r e _ s t a t e ) ) Allowed\_Return = (EL\_target \leq EL\_current) \land (EL\_target \geq 1 \lor (EL\_target = 0 \land !is\_secure\_state)) Allowed_Return=(EL_target≤EL_current)∧(EL_target≥1∨(EL_target=0∧!is_secure_state))
ARMv8引入了许多特殊的架构寄存器,用于控制和配置处理器功能。以下是一些重要的特殊寄存器:
系统控制寄存器控制内存系统操作、缓存策略等。其状态可以表示为:
M M U _ E n a b l e d = S C T L R _ E L x . M MMU\_Enabled = SCTLR\_ELx.M MMU_Enabled=SCTLR_ELx.M
A l i g n m e n t _ C h e c k = S C T L R _ E L x . A Alignment\_Check = SCTLR\_ELx.A Alignment_Check=SCTLR_ELx.A
D a t a _ C a c h e _ E n a b l e d = S C T L R _ E L x . C Data\_Cache\_Enabled = SCTLR\_ELx.C Data_Cache_Enabled=SCTLR_ELx.C
S t a c k _ A l i g n m e n t _ C h e c k = S C T L R _ E L x . S A Stack\_Alignment\_Check = SCTLR\_ELx.SA Stack_Alignment_Check=SCTLR_ELx.SA
存储异常时的返回地址,替代AArch32中的LR寄存器功能,用于异常处理。
E L R _ E L x = P C e x c e p t i o n + o f f s e t ELR\_ELx = PC_{exception} + offset ELR_ELx=PCexception+offset
其中 o f f s e t offset offset取决于异常类型。
记录异常原因和上下文信息:
E S R _ E L x = { E C [ 5 : 0 ] , I L , I S S [ 24 : 0 ] } ESR\_ELx = \{EC[5:0], IL, ISS[24:0]\} ESR_ELx={EC[5:0],IL,ISS[24:0]}
其中 E C EC EC是异常类, I L IL IL是指令长度, I S S ISS ISS是特定于异常的症状信息。
ARM浮点处理单元(VFP)提供了专门的浮点寄存器。在ARMv7架构中,有32个32位浮点寄存器S0-S31,它们可以组合成16个64位寄存器D0-D15。在ARMv8架构中,有32个128位寄存器V0-V31,可以作为浮点寄存器或SIMD寄存器使用。
浮点运算遵循IEEE 754标准,基本操作可以表示为:
加法: S d = S n + S m S_d = S_n + S_m Sd=Sn+Sm
乘法: S d = S n × S m S_d = S_n \times S_m Sd=Sn×Sm
乘加: S d = S n × S m + S a S_d = S_n \times S_m + S_a Sd=Sn×Sm+Sa
浮点表示遵循IEEE-754标准,对于单精度浮点数:
V a l u e = ( − 1 ) s × 2 e − 127 × ( 1. f ) Value = (-1)^s \times 2^{e-127} \times (1.f) Value=(−1)s×2e−127×(1.f)
其中 s s s是符号位, e e e是8位指数, f f f是23位尾数。
对于双精度浮点数:
V a l u e = ( − 1 ) s × 2 e − 1023 × ( 1. f ) Value = (-1)^s \times 2^{e-1023} \times (1.f) Value=(−1)s×2e−1023×(1.f)
其中 s s s是符号位, e e e是11位指数, f f f是52位尾数。
浮点异常处理由FPSCR(浮点状态控制寄存器)控制,包含标志位:
F P S C R = { N , Z , C , V , Q C , D N , F Z , R M o d e [ 1 : 0 ] , S t r i d e [ 1 : 0 ] , I D E , I X E , U F E , O F E , D Z E , I O E } FPSCR = \{N, Z, C, V, QC, DN, FZ, RMode[1:0], Stride[1:0], IDE, IXE, UFE, OFE, DZE, IOE\} FPSCR={N,Z,C,V,QC,DN,FZ,RMode[1:0],Stride[1:0],IDE,IXE,UFE,OFE,DZE,IOE}
其中 R M o d e RMode RMode控制舍入模式:
浮点运算的精度损失可以通过以下公式量化:
ε r e l = ∣ x e x a c t − x c o m p u t e d ∣ ∣ x e x a c t ∣ ≤ 2 − 23 \varepsilon_{rel} = \frac{|x_{exact} - x_{computed}|}{|x_{exact}|} \leq 2^{-23} εrel=∣xexact∣∣xexact−xcomputed∣≤2−23 (单精度)
ε r e l = ∣ x e x a c t − x c o m p u t e d ∣ ∣ x e x a c t ∣ ≤ 2 − 52 \varepsilon_{rel} = \frac{|x_{exact} - x_{computed}|}{|x_{exact}|} \leq 2^{-52} εrel=∣xexact∣∣xexact−xcomputed∣≤2−52 (双精度)
ARM的NEON技术是一种SIMD(单指令多数据)扩展,允许同时对多个数据元素执行相同操作。在ARMv7中,NEON与VFP共享寄存器,但提供更多的排列组合。在ARMv8中,NEON使用V0-V31寄存器。
NEON寄存器可以包含不同类型和大小的数据元素。例如,一个128位V寄存器可以包含:
SIMD运算可以表达为:
V d [ i ] = V n [ i ] ⊗ V m [ i ] V_d[i] = V_n[i] \otimes V_m[i] Vd[i]=Vn[i]⊗Vm[i] 对于 i ∈ [ 0 , l a n e s − 1 ] i \in [0, lanes-1] i∈[0,lanes−1]
其中 ⊗ \otimes ⊗表示任意运算(加、减、乘等), l a n e s lanes lanes是寄存器中的通道数量,取决于数据类型。
ARMv8.2引入了半精度(16位)浮点支持,SVE(可扩展矢量扩展)引入了可变长度的矢量寄存器,长度可以从128位到2048位不等。SVE寄存器Z0-Z31的理论算力可以计算为:
F L O P S p e a k = f C P U × l a n e s × o p e r a t i o n s _ p e r _ c y c l e FLOPS_{peak} = f_{CPU} \times lanes \times operations\_per\_cycle FLOPSpeak=fCPU×lanes×operations_per_cycle
其中 l a n e s = S V E _ V e c t o r _ L e n g t h e l e m e n t _ s i z e lanes = \frac{SVE\_Vector\_Length}{element\_size} lanes=element_sizeSVE_Vector_Length。
矢量化优化的理论加速比:
S p e e d u p = T s c a l a r T v e c t o r ≈ n n / v + o v e r h e a d Speedup = \frac{T_{scalar}}{T_{vector}} \approx \frac{n}{n/v + overhead} Speedup=TvectorTscalar≈n/v+overheadn
其中 n n n是元素数量, v v v是SIMD宽度, o v e r h e a d overhead overhead是矢量化开销。
NEON提供了丰富的数据排列和重组指令,如TBL(表查找),可以表示为:
V d [ i ] = { 0 , 如果 V n [ V m [ i ] ] > elements V n [ V m [ i ] ] , 其他情况 V_d[i] = \begin{cases} 0, & \text{如果$V_n[V_m[i]] > \text{elements}$} \\ V_n[V_m[i]], & \text{其他情况} \end{cases} Vd[i]={0,Vn[Vm[i]],如果Vn[Vm[i]]>elements其他情况
ARM架构包含许多系统寄存器,用于控制处理器功能、内存管理、异常处理等。这些寄存器通常只能在特权模式下访问。
在ARMv8中,系统寄存器使用统一的编码方案:
S < o p 0 > < o p 1 > < C n > < C m > < o p 2 > S
例如,系统控制寄存器SCTLR_EL1被编码为S3_0_C1_C0_0。
ARMv8的虚拟内存系统通过以下关键寄存器控制:
TTBRx_EL1:页表基地址寄存器,存储虚拟地址转换表的基地址。虚拟地址到物理地址的映射可以表达为:
P A = P a g e T a b l e _ L o o k u p ( V A , T T B R _ B A S E ) PA = PageTable\_Lookup(VA, TTBR\_BASE) PA=PageTable_Lookup(VA,TTBR_BASE)
其中查找过程是递归的,对于48位地址空间,使用4级页表:
L 0 _ i n d e x = ( V A > > 39 ) & 0 x 1 F F L0\_index = (VA >> 39) \& 0x1FF L0_index=(VA>>39)&0x1FF
L 1 _ i n d e x = ( V A > > 30 ) & 0 x 1 F F L1\_index = (VA >> 30) \& 0x1FF L1_index=(VA>>30)&0x1FF
L 2 _ i n d e x = ( V A > > 21 ) & 0 x 1 F F L2\_index = (VA >> 21) \& 0x1FF L2_index=(VA>>21)&0x1FF
L 3 _ i n d e x = ( V A > > 12 ) & 0 x 1 F F L3\_index = (VA >> 12) \& 0x1FF L3_index=(VA>>12)&0x1FF
O f f s e t = V A & 0 x F F F Offset = VA \& 0xFFF Offset=VA&0xFFF
MAIR_EL1:内存属性间接寄存器,定义内存访问的缓存策略。内存访问延迟可以表示为:
T a c c e s s = { T c a c h e , 如果缓存命中 T c a c h e + p × T m e m o r y , 如果缓存未命中 T_{access} = \begin{cases} T_{cache}, & \text{如果缓存命中} \\ T_{cache} + p \times T_{memory}, & \text{如果缓存未命中} \end{cases} Taccess={Tcache,Tcache+p×Tmemory,如果缓存命中如果缓存未命中
其中 p p p是缓存未命中的概率,与MAIR配置有关。
TCR_EL1:转换控制寄存器,控制虚拟地址转换的各种参数。虚拟地址空间大小由TCR的T0SZ和T1SZ字段控制:
V A _ S i z e = 64 − T 0 S Z VA\_Size = 64 - T0SZ VA_Size=64−T0SZ (对于TTBR0_EL1映射的空间)
V A _ S i z e = 64 − T 1 S Z VA\_Size = 64 - T1SZ VA_Size=64−T1SZ (对于TTBR1_EL1映射的空间)
转译后备缓冲区(TLB)加速地址转换过程,TLB命中率可以表示为:
H i t _ R a t e T L B = N h i t N t o t a l Hit\_Rate_{TLB} = \frac{N_{hit}}{N_{total}} Hit_RateTLB=NtotalNhit
TLB未命中惩罚可以表示为:
P e n a l t y T L B _ m i s s = T p a g e _ w a l k = ∑ i = 0 l e v e l s − 1 T m e m o r y _ a c c e s s Penalty_{TLB\_miss} = T_{page\_walk} = \sum_{i=0}^{levels-1} T_{memory\_access} PenaltyTLB_miss=Tpage_walk=i=0∑levels−1Tmemory_access
ARMv8提供了复杂的缓存管理指令,如DC CIVAC(数据缓存清除并使无效到PoC)。缓存一致性维护的理论模型可以表示为:
T i m e T o G l o b a l V i s i b i l i t y = m a x ( T i n t e r c o n n e c t _ l a t e n c y , T l o c a l _ c a c h e _ f l u s h ) TimeToGlobalVisibility = max(T_{interconnect\_latency}, T_{local\_cache\_flush}) TimeToGlobalVisibility=max(Tinterconnect_latency,Tlocal_cache_flush)
ARMv8引入了PMU(性能监控单元),通过一系列寄存器提供性能计数功能:
PMCR_EL0:性能监控控制寄存器,控制计数器的全局启用/禁用。
PMCCNTR_EL0:周期计数器,计算执行的CPU周期数。程序执行时间可以表示为:
T e x e c u t i o n = P M C C N T R _ E L 0 f C P U T_{execution} = \frac{PMCCNTR\_EL0}{f_{CPU}} Texecution=fCPUPMCCNTR_EL0
PMXEVCNTR_EL0:事件计数器,可以计数多种微架构事件。CPI(每指令周期数)可以计算为:
C P I = P M C C N T R _ E L 0 P M X E V C N T R _ E L 0 ( 指令计数 ) CPI = \frac{PMCCNTR\_EL0}{PMXEVCNTR\_EL0(\text{指令计数})} CPI=PMXEVCNTR_EL0(指令计数)PMCCNTR_EL0
使用这些寄存器,可以构建详细的性能分析模型:
T e x e c u t i o n = N i n s t r u c t i o n s × C P I × 1 f C P U T_{execution} = N_{instructions} \times CPI \times \frac{1}{f_{CPU}} Texecution=Ninstructions×CPI×fCPU1
其中 C P I CPI CPI可以进一步分解为:
C P I = C P I b a s e + C P I m e m o r y + C P I b r a n c h + C P I d e p e n d e n c i e s CPI = CPI_{base} + CPI_{memory} + CPI_{branch} + CPI_{dependencies} CPI=CPIbase+CPImemory+CPIbranch+CPIdependencies
ARM架构定义了标准的调用约定,规定了寄存器在函数调用中的使用方式。在ARM AAPCS(ARM架构过程调用标准)中:
函数调用的栈帧动态演化可以表示为:
f r a m e c a l l e r → p u s h ( L R ) → a l l o c a t e ( f r a m e c a l l e e ) → f u n c t i o n _ b o d y → d e a l l o c a t e ( f r a m e c a l l e e ) → p o p ( L R ) → f r a m e c a l l e r frame_{caller} \rightarrow push(LR) \rightarrow allocate(frame_{callee}) \rightarrow function\_body \rightarrow deallocate(frame_{callee}) \rightarrow pop(LR) \rightarrow frame_{caller} framecaller→push(LR)→allocate(framecallee)→function_body→deallocate(framecallee)→pop(LR)→framecaller
在尾调用优化中,如果函数B是函数A的最后一个调用,则可以重用A的栈帧,避免创建B的新栈帧:
f r a m e A → . . . → f u n c t i o n _ B _ b o d y → d e a l l o c a t e ( f r a m e A ) → r e t u r n frame_A \rightarrow ... \rightarrow function\_B\_body \rightarrow deallocate(frame_A) \rightarrow return frameA→...→function_B_body→deallocate(frameA)→return
这样可以将递归调用优化为迭代形式,从 O ( n ) O(n) O(n)空间复杂度优化到 O ( 1 ) O(1) O(1)。
ADDS X0, X1, X2 ; X0 = X1 + X2,更新标志位
数学表达式: X 0 = X 1 + X 2 X0 = X1 + X2 X0=X1+X2,同时:
N = X 0 [ 63 ] N = X0[63] N=X0[63]
Z = ( X 0 = = 0 ) ? 1 : 0 Z = (X0 == 0) ? 1 : 0 Z=(X0==0)?1:0
C = ( X 1 + X 2 > 2 64 − 1 ) ? 1 : 0 C = (X1 + X2 > 2^{64} - 1) ? 1 : 0 C=(X1+X2>264−1)?1:0
V = ( ( X 1 [ 63 ] = = X 2 [ 63 ] ) ∧ ( X 0 [ 63 ] ≠ X 1 [ 63 ] ) ) ? 1 : 0 V = ((X1[63] == X2[63]) \land (X0[63] \neq X1[63])) ? 1 : 0 V=((X1[63]==X2[63])∧(X0[63]=X1[63]))?1:0
MULLT R0, R1, R2 ; 如果N≠V,则R0 = R1 × R2
数学表达式: R 0 = R 1 × R 2 R0 = R1 \times R2 R0=R1×R2 当且仅当 N ≠ V N \neq V N=V
对于64位乘法,结果可能是128位:
MUL X0, X1, X2 ; X0 = low64(X1 × X2)
UMULH X3, X1, X2 ; X3 = high64(X1 × X2)
完整结果表示为: r e s u l t = X 3 × 2 64 + X 0 result = X3 \times 2^{64} + X0 result=X3×264+X0
FMLA V0.4S, V1.4S, V2.4S ; V0.4S[i] = V0.4S[i] + (V1.4S[i] × V2.4S[i]) 对于i∈[0,3]
数学表达式: V 0.4 S [ i ] = V 0.4 S [ i ] + ( V 1.4 S [ i ] × V 2.4 S [ i ] ) V0.4S[i] = V0.4S[i] + (V1.4S[i] \times V2.4S[i]) V0.4S[i]=V0.4S[i]+(V1.4S[i]×V2.4S[i]) 对于 i ∈ [ 0 , 3 ] i \in [0,3] i∈[0,3]
向量化卷积运算可以表示为:
O u t p u t [ i ] = ∑ j = 0 k e r n e l _ s i z e − 1 I n p u t [ i + j ] × K e r n e l [ j ] Output[i] = \sum_{j=0}^{kernel\_size-1} Input[i+j] \times Kernel[j] Output[i]=j=0∑kernel_size−1Input[i+j]×Kernel[j]
转换为NEON指令后,使用循环展开和并行计算显著提高性能。
寄存器是最快的存储资源,因此优化寄存器使用对性能至关重要。一些关键优化技术包括:
编译器尝试将变量分配到寄存器中以减少内存访问。优化问题可以表示为:
最小化 ∑ i = 1 n c o s t ( v i ) × m e m _ a c c e s s ( v i ) \sum_{i=1}^{n} cost(v_i) \times mem\_access(v_i) ∑i=1ncost(vi)×mem_access(vi)
其中 v i v_i vi是变量, c o s t cost cost是访问内存的成本, m e m _ a c c e s s mem\_access mem_access是内存访问次数。这是一个NP完全问题,可以通过图着色算法近似解决。对于具有干涉的变量 v i v_i vi和 v j v_j vj,不能分配到同一个寄存器,构建干涉图:
G = ( V , E ) G = (V, E) G=(V,E),其中 V V V是变量集合, E = { ( v i , v j ) ∣ l i f e t i m e ( v i ) ∩ l i f e t i m e ( v j ) ≠ ∅ } E = \{(v_i, v_j) | lifetime(v_i) \cap lifetime(v_j) \neq \emptyset\} E={(vi,vj)∣lifetime(vi)∩lifetime(vj)=∅}
然后使用启发式算法为图着色,颜色数量不超过可用寄存器数量。
循环展开通过减少循环控制开销来优化寄存器使用。例如,原始循环:
for (i = 0; i < 100; i++) {
sum += array[i];
}
展开后:
for (i = 0; i < 100; i += 4) {
sum1 += array[i];
sum2 += array[i+1];
sum3 += array[i+2];
sum4 += array[i+3];
}
sum = sum1 + sum2 + sum3 + sum4;
软件流水线将循环迭代重叠执行,可以表示为:
i t e r a t i o n n s t a g e 1 ∣ ∣ i t e r a t i o n n − 1 s t a g e 2 ∣ ∣ i t e r a t i o n n − 2 s t a g e 3 iteration_n^{stage_1} || iteration_{n-1}^{stage_2} || iteration_{n-2}^{stage_3} iterationnstage1∣∣iterationn−1stage2∣∣iterationn−2stage3
其中 ∣ ∣ || ∣∣表示并行执行。理论加速比为:
S p e e d u p = s t a g e s × i t e r a t i o n s s t a g e s + i t e r a t i o n s − 1 ≈ s t a g e s Speedup = \frac{stages \times iterations}{stages + iterations - 1} \approx stages Speedup=stages+iterations−1stages×iterations≈stages (当 i t e r a t i o n s ≫ s t a g e s iterations \gg stages iterations≫stages时)
现代ARM处理器采用寄存器重命名技术解决写后读(WAR)和写后写(WAW)依赖问题。物理寄存器数量通常多于架构寄存器数量:
n p h y s i c a l > n a r c h i t e c t u r a l n_{physical} > n_{architectural} nphysical>narchitectural
重命名过程可以表示为映射函数:
m a p : R a r c h × v e r s i o n → R p h y s map: R_{arch} \times version \rightarrow R_{phys} map:Rarch×version→Rphys
乱序执行的指令吞吐量可以表示为:
I P C m a x = m i n ( i s s u e _ w i d t h , n r e a d y c y c l e ) IPC_{max} = min(issue\_width, \frac{n_{ready}}{cycle}) IPCmax=min(issue_width,cyclenready)
其中 n r e a d y n_{ready} nready是每个周期准备好执行的指令数量,取决于依赖关系图的临界路径。
当寄存器分配无法将所有活跃变量映射到物理寄存器时,需要将一些变量溢出到栈上。溢出成本可以表示为:
C o s t s p i l l = f r e q ( v ) × ( c o s t s t o r e + c o s t l o a d ) Cost_{spill} = freq(v) \times (cost_{store} + cost_{load}) Costspill=freq(v)×(coststore+costload)
其中 f r e q ( v ) freq(v) freq(v)是变量 v v v的访问频率。
ARM编译器通常采用线性扫描或图着色算法选择溢出变量,最小化总体溢出成本。栈帧结构优化可以减少内存访问:
f r a m e _ s i z e = ∑ i s i z e ( l o c a l _ v a r i ) + ∑ j s i z e ( s p i l l e d _ v a r j ) + s i z e ( s a v e d _ r e g i s t e r s ) + s i z e ( a l i g n m e n t _ p a d d i n g ) frame\_size = \sum_{i} size(local\_var_i) + \sum_{j} size(spilled\_var_j) + size(saved\_registers) + size(alignment\_padding) frame_size=i∑size(local_vari)+j∑size(spilled_varj)+size(saved_registers)+size(alignment_padding)
现代编译器使用堆栈合并技术,分析变量生命周期,重用栈空间,优化公式为:
f r a m e _ s i z e o p t = m a x t i m e p o i n t { ∑ v ∣ a l i v e ( v , t i m e p o i n t ) s i z e ( v ) } + s i z e ( s a v e d _ r e g i s t e r s ) + s i z e ( a l i g n m e n t _ p a d d i n g ) frame\_size_{opt} = max_{timepoint} \{ \sum_{v | alive(v, timepoint)} size(v) \} + size(saved\_registers) + size(alignment\_padding) frame_sizeopt=maxtimepoint{v∣alive(v,timepoint)∑size(v)}+size(saved_registers)+size(alignment_padding)