Armar notas de estudio de ensamblaje (9): código de sucursal eficiente y acceso a datos no alineados

El cambio de código de bifurcación (x) es muy común en nuestro código ordinario, y también es una operación que consume bastante tiempo. Si se optimiza, la eficiencia del código puede mejorarse enormemente.

1. Para códigos de ramificación de tipo 0 <= x <N

En este caso, N no puede ser demasiado grande. Para el siguiente código 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();
     } 
}
Podemos utilizar el valor del registro de PC como referencia y el valor de x como índice para lograrlo. El código de ensamblaje optimizado es el siguiente:
           ; 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 es un valor ordinario

Si encuentra que x no sigue la forma de 0 <= x <N, o N es muy grande, el método anterior obviamente no es aplicable. En este caso, podemos usar la función hash para mapear, es decir, y = f (x), que se puede convertir en la forma de 0 <= y <N, con y = f (x) en lugar de x como condición para el juicio de rama , Para que podamos usar el método anterior.
Por ejemplo, suponga que cuando x = 2 ^ k, se llama a la función method_k, es decir, el valor de x es 1, 2, 4, 8, 16, 32, 64, 128 y otros valores llaman a la función predeterminada method_d. Necesitamos encontrar una función hash compuesta de varias potencias de 2 menos una multiplicación (este método es más eficiente en ARM, y se puede lograr un desplazamiento directo). A través de los experimentos, se encuentra que los dígitos 9-11 de los números obtenidos por los 8 valores anteriores x * 15 * 31 son diferentes, podemos usar esta característica para lograr saltos de rama a través de operaciones de bits.
El siguiente es el código de ensamblaje optimizado:
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

El método anterior es solo un caso especial que citamos. En el caso de que x sea una potencia distinta de 2, aún podemos usar un método similar para lograrlo. Aquí solo se proporciona una idea.

3. Acceso a datos no alineados

Se debe evitar el acceso a datos alineados sin dirección tanto como sea posible, de lo contrario es perjudicial para la portabilidad y la eficiencia.
  • El método de acceso más simple es leer y escribir en unidades de un byte o media palabra. Este método es más recomendable, pero la eficiencia es relativamente baja.
El siguiente código lee datos de 32 bits no alineados con la dirección. Utilizamos t0, t1, t2 tres registros para leer para evitar el enclavamiento de la tubería. Cada lectura de datos no alineados con direcciones en ARM9TDMI requiere 7 ciclos de reloj. El siguiente ejemplo enumera las versiones correspondientes a little_endian y 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

Publicado 60 artículos originales · Me gusta 44 · Visitas 340,000+

Supongo que te gusta

Origin blog.csdn.net/beyond702/article/details/52251084
Recomendado
Clasificación