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