Arm汇编学习笔记(九)——高效的分支代码及非对齐数据的访问

ARM 478浏览

 

分支代码switch(x)在我们平常的代码中是非常常见的,而且也是比较耗时的操作,如果优化以后可以对代码的效率有很大提升。

1. 对于0 <= x < N类型的分支代码

此种情况N不能太大,对于下面C代码:
int ref_switch(int x)
{
     switch (x) {
                 case 0: return method_0();
                 case 1: return method_1();
                 case 2: return method_2();
                 case 3: return method_3();
                 case 4: return method_4();
                 case 5: return method_5();
                 case 6: return method_6();
                 case 7: return method_7();
                 default: return method_d();
     } 
}

我们可以以pc寄存器的值为基准,以x的值作为索引来实现,优化的汇编代码如下:

           ; int switch_relative(int x)
switch_relative
         MP     x, #8
         ADDLT   pc, pc, x, LSL#2
         B       method_d
         B       method_0
         B       method_1
         B       method_2
         B       method_3
         B       method_4
         B       method_5
         B       method_6
         B       method_7

2. x是一个普通的值

如果遇到x不遵循0 <= x < N这种形式,或者N非常大,上面那种方式显然不适用了。这种情况下我们可以用hashing function来映射一下,即y = f(x),可以将其转换成0 <= y < N的形式,以y = f(x)而不是x作为分支判断的条件,这样我们就可以采用上面的方法了。
举例假设,当x = 2^k时,调用method_k函数,即x的取值有1,2,4,8,16,32,64,128,其它值调用默认函数method_d。我们需要找到一个hash函数由2的若干次方减一相乘组成(这种方式在ARM上效率比较高,直接位移就可以实现)。经过实验发现,对于上面8个值x * 15 * 31得到的数的第9-11位是不同的,我们可以利用这个特点通过位运算来实现分支跳转。
下面是优化后的汇编代码:
x RN0
hash RN 1
                     ; int switch_hash(int x)
switch_hash
                     RSB     hash, x, x, LSL#4             ; hash=x*15
                     RSB     hash, hash, hash, LSL#5   ; hash=x*15*31
                     AND hash, hash, #7 << 9           ; mask out the hash value
                     ADD pc, pc, hash, LSR#6 
                     NOP
                     TEQ x, #0x01
                     BEQ     method_0
                     TEQ     x, #0x02
                     BEQ     method_1
                     TEQ     x, #0x40
                     BEQ     method_6
                     TEQ     x, #0x04
                     BEQ     method_2
                     TEQ     x, #0x80
                     BEQ     method_7
                     TEQ     x, #0x20
                     BEQ     method_5
                     TEQ     x, #0x10
                     BEQ     method_4
                     TEQ     x, #0x08
                     BEQ     method_3
                     B       method_d

上面的方法只是我们举出的一个特例,在x为非2的幂的情况下,我们依然可以使用相似的方法来实现。这里仅仅提供一种思路。

 

3. 非对齐数据访问

对于非地址对齐的数据访问应该尽量避免,否则对可移植性和效率都是不利的。
  • 最简单的访问方法是以一个字节或半字为单位进行读写,这种方法是比较推荐的,但是效率相对比较低。

下面代码读取一个非地址对齐的32位数据,我们使用t0,t1,t2三个寄存器去读取以防止流水线互锁。在ARM9TDMI上每个非地址对齐的数据读取需要消耗7个时钟周期。下面的例子中列出了little_endian和big_endian对应的版本。

p RN0 x RN1
t0 RN 2
t1 RN 3
t2 RN 12
                     ; int load_32_little(char *p)
load_32_little
                     LDRB    x,  [p]
                     LDRB    t0, [p, #1]
                     LDRB    t1, [p, #2]
                     LDRB    t2, [p, #3]
                     ORR     x, x, t0, LSL#8
                     ORR     x, x, t1, LSL#16
                     ORR     r0, x, t2, LSL#24
                     MOV     pc, lr
                     ; int load_32_big(char *p)
load_32_big
                     LDRB    x,  [p]
                     LDRB    t0, [p, #1]
                     LDRB    t1, [p, #2]
                     LDRB    t2, [p, #3]
                     ORR     x, t0, x, LSL#8
                     ORR     x, t1, x, LSL#8
                     ORR     r0, t2, x, LSL#8
                     MOV     pc, lr
                   ; void store_32_little(char *p, int x)
store_32_little
                   STRB    x,  [p]
                   MOV     t0, x, LSR#8
                   STRB    t0, [p, #1]
                   MOV     t0, x, LSR#16
                   STRB    t0, [p, #2]
                   MOV     t0, x, LSR#24
                   STRB    t0, [p, #3]
                   MOV     pc, lr
                   ; void store_32_big(char *p, int x)
store_32_big
                   MOV t0, x, LSR#24
                   STRB t0, [p]
                   MOV t0, x, LSR#16
                   STRB t0, [p, #1]
                   MOV t0, x, LSR#8
                   STRB t0, [p, #2]
                   STRB x, [p, #3]
                   MOV pc,lr