Otimização do XC8 - fiquei de cara!

Estava eu ontem fazendo (pela milésima vez) uma biblioteca prá acionar um display LCD alfanumérico de 16x2 caracteres num PIC18. Por que eu estava escrevendo de novo? Porque eu sempre acho que dá prá melhorar, então eu refaço e... não tente entender.
Aí estava fazendo uma rotina que recebe um nibble e escreve os 4 bits menos significativos no 4 bits do display, normal. Normalmente, esta rotina é algo do tipo
Aí eu pensei "ah, mas isso é tosco. O PIC18 tem uma instrução chamada Bittest (BTFSS e BTFSC) que serve exatamente prá isso. Fazer esse mascaramento e esse if é muito pouco otimizado". Eu lembrei que o CCS possui o comando bittest() exatamente prá isso. Dei uma procurada no manual do XC8, mas não tem nada parecido com bittest.
Aí eu me toquei que o XC8 usa umas structs com bitfields prá acessar bits de registradores.
Quebrei a cabeça (porque eu me bato com essas coisas exotéricas do C) e consegui criar uma struct com uma union que tinha um bitfield, eu jogava o valor recebido da função prá dentro e acessava os valores bit por bit. Não vou colar aqui porque eu apaguei e esqueci como eu fiz (já vai entender o motivo).
Aí compilei, fui ver o assembly, e tadá: ficou lindo, no assembly ele usava BTFSS prá testar o bit, a manipulava os bits do registrador prá escrever na GPIO.
Eu estava explodindo de orgulho (é que eu realmente sou muito noob prá essas coisas diferentonas do C), mas eu queria a cereja do bolo. Queria comparar quanto minha implementação era mais otimizada que usar um monte de if.
Refiz a rotina, mas prá economizar dedos, usei operadores ternários (depois de uns 20 anos olhando feio prá esse treco, eu meio que consigo entender agora como usar essa bagaça). A rotina ficou:
É basicamente exatamente igual ao que fiz acima, só troquei os if else por um ternário. Em C ficou curtinho, mas isso não tem a menor relevância, o que importa é o assembly. Minha decepção foi esta:
Onde está a operação de &? Onde está o if? Ond está o else? Poisé, esse malditinho otimizou o operador ternário em um único BTFSS. E o pior: ficou exatamente perfeitamente igual ao que ele compilou usando bit fields. Com a diferença que, prá mim, usar operadores ternário ainda é muitas vezes mais simples de entender do que aquela estratura com unions e bit fields.
Só queria compartilhar a experiência. Muitas vezes é bem legal olhar o assembly gerado, prá se tocar que às vezes fazer aqueles malabarismos todos em C que deixam o código impossível de entender, acaba gerando o mesmo assembly de um algoritmo simples e elegante.
Aí estava fazendo uma rotina que recebe um nibble e escreve os 4 bits menos significativos no 4 bits do display, normal. Normalmente, esta rotina é algo do tipo
- Código: Selecionar todos
void escreve_nibble(unsigned char valor)
{
if(valor & 0x01)
LATBbits.LATB0 = 1;
else
LATBbits.LATB0 = 0;
if(valor & 0x02)
LATBbits.LATB3 = 1;
else
LATBbits.LATB3 = 0;
if(valor & 0x04)
LATCbits.LATC1 = 1;
else
LATCbits.LATC1 = 0;
if(valor & 0x08)
LATBbits.LATB5 = 1;
else
LATBbits.LATB5 = 0;
Aí eu pensei "ah, mas isso é tosco. O PIC18 tem uma instrução chamada Bittest (BTFSS e BTFSC) que serve exatamente prá isso. Fazer esse mascaramento e esse if é muito pouco otimizado". Eu lembrei que o CCS possui o comando bittest() exatamente prá isso. Dei uma procurada no manual do XC8, mas não tem nada parecido com bittest.
Aí eu me toquei que o XC8 usa umas structs com bitfields prá acessar bits de registradores.
Quebrei a cabeça (porque eu me bato com essas coisas exotéricas do C) e consegui criar uma struct com uma union que tinha um bitfield, eu jogava o valor recebido da função prá dentro e acessava os valores bit por bit. Não vou colar aqui porque eu apaguei e esqueci como eu fiz (já vai entender o motivo).
Aí compilei, fui ver o assembly, e tadá: ficou lindo, no assembly ele usava BTFSS prá testar o bit, a manipulava os bits do registrador prá escrever na GPIO.
Eu estava explodindo de orgulho (é que eu realmente sou muito noob prá essas coisas diferentonas do C), mas eu queria a cereja do bolo. Queria comparar quanto minha implementação era mais otimizada que usar um monte de if.
Refiz a rotina, mas prá economizar dedos, usei operadores ternários (depois de uns 20 anos olhando feio prá esse treco, eu meio que consigo entender agora como usar essa bagaça). A rotina ficou:
- Código: Selecionar todos
void write_nibble(unsigned char val)
{
val & 0x01 ? LATBbits.LATB0 = 1 : LATBbits.LATB0 = 0;
val & 0x02 ? LATBbits.LATB3 = 1 : LATBbits.LATB3 = 0;
val & 0x04 ? LATCbits.LATC1 = 1 : LATCbits.LATC1 = 0;
val & 0x08 ? LATBbits.LATB5 = 1 : LATBbits.LATB5 = 0;
}
É basicamente exatamente igual ao que fiz acima, só troquei os if else por um ternário. Em C ficou curtinho, mas isso não tem a menor relevância, o que importa é o assembly. Minha decepção foi esta:
- Código: Selecionar todos
6: val & 0x01 ? DB4 = 1 : DB4 = 0;
11CB 1C70 BTFSS __pcstackCOMMON, 0x0
11CC 29D0 GOTO 0x1D0
11CD 0022 MOVLB 0x2
11CE 140E BSF LATC, 0x0
11CF 29D2 GOTO 0x1D2
11D0 0022 MOVLB 0x2
11D1 100E BCF LATC, 0x0
Onde está a operação de &? Onde está o if? Ond está o else? Poisé, esse malditinho otimizou o operador ternário em um único BTFSS. E o pior: ficou exatamente perfeitamente igual ao que ele compilou usando bit fields. Com a diferença que, prá mim, usar operadores ternário ainda é muitas vezes mais simples de entender do que aquela estratura com unions e bit fields.
Só queria compartilhar a experiência. Muitas vezes é bem legal olhar o assembly gerado, prá se tocar que às vezes fazer aqueles malabarismos todos em C que deixam o código impossível de entender, acaba gerando o mesmo assembly de um algoritmo simples e elegante.