Consejo de código con ciclos de reloj mínimos

0

Mi pregunta original fue:

  

Tengo datos de 8 bits con solo 3 bits utilizados, por ejemplo:

     

0110 0001

     

Donde 0 indica el bit no utilizado que siempre se establece en 0 y 1 indica   bits que cambian.

     

Quiero convertir este 0110 0001 8 bits a 3 bits que indican este 3   bits utilizados.

     

Por ejemplo

     

0110 0001 - > 111

     

0010 0001 - > 011

     

0000 0000 - > 000

     

0100 0001 - > 101

     

¿Cómo puedo hacer eso con operaciones mínimas?

Tuve esta respuesta:

a = 0110 0001;

data = ((a >> 4) & 6) | (a & 1)

Pero antes de esta respuesta utilicé este método:

a = 0110 0001;

if(a&0x01)
 data = data + 1;
if(a&0x20)
 data = data + 2;    
if(a&0x40)
 data = data + 4;

Quiero preguntar cuál es más eficiente para una MCU y requiere menos ciclo de reloj para realizarla. (Sé que eso depende de MCU, pero lo pregunto de manera general)

    
pregunta Yaro

3 respuestas

4

Como lo menciona @sharptooth, el compilador realmente tiene la última palabra. También depende en gran medida de la arquitectura para la que esté compilando, qué instrucciones tiene disponibles.

Por ejemplo, al compilar su primer fragmento de código para los resultados de MIPS:

sra v1,v0,0x4
andi    v1,v1,0x6
andi    v0,v0,0x1
or  v0,v1,v0

Y tu segundo resultado en:

andi    v1,v0,0x1
beqz    v1,func+0x20
andi    v1,v0,0x20
lbu v1,-32760(gp)
addiu   v1,v1,1
sb  v1,-32760(gp)
andi    v1,v0,0x20
beqz    v1,func+0x34
andi    v0,v0,0x40
lbu v1,-32760(gp)
addiu   v1,v1,2
sb  v1,-32760(gp)
beqz    v0,func+0x44
lbu v0,-32760(gp)
addiu   v0,v0,4
sb  v0,-32760(gp)

Indudablemente, en esta situación, su primer fragmento es el código más eficiente. Puede que no siempre sea igual para una arquitectura diferente o un compilador diferente.

También hay algunas otras advertencias a tener en cuenta con su segundo fragmento. Principalmente, ¿qué son los "datos" al principio? Debe recordar que siempre debe poner a cero los "datos" antes de realizar sus cálculos, o terminará acumulando valores sucesivos. Lo mismo no es cierto para el primer fragmento.

    
respondido por el Majenko
1

Como otros han dicho correctamente, "depende".

En un Cortex M0, con las variables en la memoria, para tu one-liner obtengo

 // data = ((a >> 4) & 6) | (a & 1)
 ldrb   r1, [r2]
 ldrb   r3, [r2]
 mov    r2, #1
 lsr    r1, r1, #4
 and    r1, r4
 and    r2, r3
 mov    r3, r1
 orr    r3, r2
 strb   r3, [r5]

para tu segundo código

 // if's 
 mov    r3, sp
 ldrb   r1, [r2]
 add    r3, r3, #6
 lsl    r1, r1, #31
 bpl    .L2
 ldrb   r1, [r3]
 add    r1, r1, #1
 uxtb   r1, r1
 strb   r1, [r3]
.L2:
 ldrb   r1, [r2]
 lsl    r1, r1, #26
 bpl    .L3
 ldrb   r1, [r3]
 add    r1, r1, #2
 uxtb   r1, r1
 strb   r1, [r3]
.L3:
 ldrb   r1, [r2]
 lsl    r1, r1, #25
 bpl    .L4
 ldrb   r1, [r3]
 add    r1, r1, #4
 uxtb   r1, r1
 strb   r1, [r3]
.L4:

Yuk, eso es feo, ¿verdad? Pero en una arquitectura que tiene instrucciones de omisión (PIC) o instrucciones condicionales (ARM), podría verse mucho mejor.

Mi intento sería

data = ((a >> 4 ) | a ) & 0x0F;

ldrb    r2, [r3, #7]
ldrb    r1, [r3, #7]
lsr r2, r2, #4
orr r2, r1
mov r1, #15
and r2, r1
strb    r2, [r3, #6]

Lo que resulta marginalmente mejor que su de una sola línea en esta versión de este compilador para este objetivo y con esta configuración de optimización .

Creo que incluso podría omitir el final "& 0x0F" que ahorraría 2 instrucciones.

    
respondido por el Wouter van Ooijen
0

En el PIC de Microchip, la prueba de bits individuales es generalmente el enfoque más rápido si la fuente o el destino están en el mismo banco, o si al menos uno de ellos es una región no bancarizada. En esas máquinas, cada operación de conjunto de bits condicional tomará dos instrucciones (una para el "si" y otra para el "conjunto de bits"). En el 8051, el enfoque más rápido si la fuente y el destino están en la memoria direccionable por bits será usar las instrucciones "mov bit, C" y "mov C, bit".

En el ARM, el enfoque "general" más veloz puede ser el uso de una combinación de RORS o ROLS y ADC. Este enfoque puede utilizarse para permutar bits de manera arbitraria entre los registros y un costo de dos ciclos por bit. Si, por ejemplo, uno quisiera ensamblar un registro R2 utilizando (en primer orden LSB) los bits R0.3, R1.4, R0.8 y R1.7, la secuencia sería algo así como:

rors r1,r1,#7 ; Move bit 7 to bit 0
ands r2,r1,#1  ; May need to split this into two instructions on some machines
rors r0,r0,#9 ; Move bit 8 into carry and bit 31
adcs r2,r2,r2
rors r1,r1,#30 ; Bit 4 had been at 29, so move it to carry and bit 31
adcs r2,r2,r2
rors r0,r0,#27 ; Bit 3 had been at 26; so move it to carry and bit 31
adcs r2,r2,r2

Desafortunadamente, no conozco ninguna forma agradable de convencer a un compilador de C para que genere algo como el anterior, pero el código de ensamblaje no es complicado. La instrucción RORS hace un giro circular hacia la derecha, copiando el último bit desplazado en el registro de acarreo. La instrucción ADCS agrega un registro a sí mismo (cambiando efectivamente a la izquierda un lugar) pero agrega uno si la operación anterior establece el indicador de acarreo.

    
respondido por el supercat

Lea otras preguntas en las etiquetas