Otimização do XC8 - fiquei de cara!

Software e Hardware para uC PIC

Moderadores: andre_luis, 51, guest2003, Renie

Otimização do XC8 - fiquei de cara!

Mensagempor xultz » 06 Out 2017 09:09

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
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.
98% das vezes estou certo, e não estou nem aí pros outros 3%.
Avatar do usuário
xultz
Dword
 
Mensagens: 3001
Registrado em: 13 Out 2006 18:41
Localização: Curitiba

Re: Otimização do XC8 - fiquei de cara!

Mensagempor KrafT » 06 Out 2017 10:11

Vc usa a versão free? Ele "desotimiza" as coisas de propósito...

Bom, eu nunca olho bibliotecas que estejam funcionando e quando eu não tenho restrições de espaço...
"..."Come to the edge," he said. And so they came. And he pushed them. And they flew."― Guillaume Apollinaire
Avatar do usuário
KrafT
Dword
 
Mensagens: 2228
Registrado em: 11 Out 2006 14:15
Localização: Blumenau -SC

Re: Otimização do XC8 - fiquei de cara!

Mensagempor xultz » 06 Out 2017 10:30

Sim, estou usando a versão free, e esse aí é o assembly que ele gerou.
Uma vez eu vi uma comparação que um cara fez, e a versão free enfia umas instruções sem sentido principalmente em calls, eu estava esperando encontrar alguma bobagem nessa saída, mas esse trechinho ficou bem otimizado. A única otimização que consigo imaginar, seria executar a instrução MOVLB 0x2 (que seleciona a página de memória onde está mapeado o registrador LATB) uma única vez no começo do trecho. Ele executa essa instrução uma só vez, mas poderia ter economizado uma posição de memória desta forma. Fora isso, não consigo imaginar como deixar esse assembly mais otimizado.
98% das vezes estou certo, e não estou nem aí pros outros 3%.
Avatar do usuário
xultz
Dword
 
Mensagens: 3001
Registrado em: 13 Out 2006 18:41
Localização: Curitiba

Re: Otimização do XC8 - fiquei de cara!

Mensagempor KrafT » 06 Out 2017 10:45

Cara... Me desculpe, mas otimizar o assembly é para quem tá com a vida ganha :D :D :D
"..."Come to the edge," he said. And so they came. And he pushed them. And they flew."― Guillaume Apollinaire
Avatar do usuário
KrafT
Dword
 
Mensagens: 2228
Registrado em: 11 Out 2006 14:15
Localização: Blumenau -SC

Re: Otimização do XC8 - fiquei de cara!

Mensagempor msamsoniuk » 07 Out 2017 01:26

salve xuxu-super-hackerz! :D

na realidade vc usou o operador ternario como se fosse um if, ou seja:

teste&mask ? variavel.bit=1 : variavel.bit=0;

exatamente igual a:

if(teste&mask) variavel.bit=1; else variavel.bit=0;

isso pode ser feito, mas na realidade a ideia do operador ternario eh ser usado de outra forma:

variavel.bit = teste&mask ? 1 : 0;

note como fica compacto e de aspecto profissa!

mas vc pode escrever de forma mais compacta, obfuscada e exoterica ainda:

variavel.bit = !!(teste&mask);

o (val & 0x02), por exemplo, vai ser 0x02 se for valido, entao !(val&0x02) era 0x02 e vira 0x00, mas se fizer !!(val&0x02) entao era 0x02, vira 0x00 e vira 0x01 novamente.

e pq nao fazer var um bitfield e puxar diretamente? algo como:

variavel.bit = teste.bit;

mas perceba que isso tudo nao eh realmente tao eficiente: vc tem 4 operadores para manipular um nibble e, bit a bit, setar o outro nibble... sera que nao tem como fazer isso tudo em paralelo e de uma vez soh? acredito que sim! e o truque eh simples: na medida que var tem apenas 4 bits, vc tem apenas 16 resultados possiveis. imagino tambem que LATBbits eh uma variavel de 8 bits e, exceto pelos bits 0, 1 e 3, o resto provavelmente nao importa ou vc nao quer mexer.

entao vc criaria uma tabela da seguinte forma no prompt do seu linux ae:

# for((i=0;i!=16;i++)); do echo -n "$[(i&1?1:0)+(i&2?8:0)+(i&8?32:0)], "; done; echo
0, 1, 8, 9, 0, 1, 8, 9, 32, 33, 40, 41, 32, 33, 40, 41,


o for ali conta de 0 a 15 e isso gera todas as combinacoes possiveis de var. daih no echo ele faz o var&mascara como vc faz e, usando um operador ternario, atribui 1 ou 0 para o respectivo bit de LATBbits (suponho que LATB0 seja o bit 0, LATB3 seja o bit 3, etc).

dessa saida vc copia e cola a parte relevante no seu codigo:

const unsigned char lookup[16] = { 0, 1, 8, 9, 0, 1, 8, 9, 32, 33, 40, 41, 32, 33, 40, 41 };
LATBbits = lookup[var];


isso se os outros bits nao importam na variavel destino (serao escritos como zero). se eles importam, entao tem ler eles, mascarar corretamente e juntar com o resultado do lookup[]:

const unsigned char lookup[16] = { 0, 1, 8, 9, 0, 1, 8, 9, 32, 33, 40, 41, 32, 33, 40, 41 };
LATBbits = lookup[var] | (LATBbits&0xD6);


no caso, 0xD6 (11010110b) justamente zera apenas os bits 0, 3 e 5, que serao simultaneamente configurados por lookup[] de acordo com a variavel var de entrada, em uma unica operacao.

pena que o 4.o bit esta em outra variavel destino, senao daria para fazer tudo em uma unica operacao ao inves de 4 (no caso, vai ter que fazer outra operacao a parte para a outra variavel). se eh mais eficiente nao sei... com poucos bits, provavelmente bitfield bit a bit eh mais eficiente. qdo o numero de bits aumenta, lookup costuma comecar a ser mais eficiente... mas comeca tb a ocupar mais memoria tb! e tem o custo da lookup[], que depende do processador... normalmente o acesso a lookup tables eh mais otimizado e tem menos custo que um teste e salto condicional, pq mantem os pipelines sempre cheios.

mas as vezes nem eh tanto questao de ser eficiente... eh justamente testando essas coisas que vc comeca a pensar fora da caixa e tem ideias que podem ser usadas no futuro em outras aplicacoes! ;D
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04

Re: Otimização do XC8 - fiquei de cara!

Mensagempor KrafT » 07 Out 2017 10:42

Sei lá, se o que você faz te faz feliz, está tudo bem. Desde que não seja nocivo ao outro e à coletividade.
"..."Come to the edge," he said. And so they came. And he pushed them. And they flew."― Guillaume Apollinaire
Avatar do usuário
KrafT
Dword
 
Mensagens: 2228
Registrado em: 11 Out 2006 14:15
Localização: Blumenau -SC

Re: Otimização do XC8 - fiquei de cara!

Mensagempor pamv » 07 Out 2017 13:32

KrafT escreveu:Sei lá, se o que você faz te faz feliz, está tudo bem. Desde que não seja nocivo ao outro e à coletividade.

E no final o XC8 deve gerar o mesmo ASM pra todas as alternativas...
pamv
Word
 
Mensagens: 842
Registrado em: 20 Jun 2016 21:47

Re: Otimização do XC8 - fiquei de cara!

Mensagempor xultz » 09 Out 2017 08:22

Marcelo, vou tentar resumir prá você.

Eu fiz uma rotina usando bit field prá manipular os bits do registradores.
Depois eu fiz uma rotina que checa os bits usando máscara e operadores ternários, que é exatamente um if mas com aparência mais criptografada, mais difícil de entender e menos profissional.

Eu comparei o assembly gerado pelas duas rotinas. O compilador gerou um código exatamente igual, sem tirar nem por uma única instrução.

Neste caso, usando este compilador, para este PIC, não importa quão obscuro e criptografado você faça teu código C, o resultado do assembly será exatamente o mesmo (ou talvez pior). O código assembly que eu colei mostra o resultado, e em ambos casos eram exatamente iguais, e olhando ele, não consegui imaginar um código em assembly mais otimizado que aquele gerado.

Me fiz entender agora?
98% das vezes estou certo, e não estou nem aí pros outros 3%.
Avatar do usuário
xultz
Dword
 
Mensagens: 3001
Registrado em: 13 Out 2006 18:41
Localização: Curitiba

Re: Otimização do XC8 - fiquei de cara!

Mensagempor msamsoniuk » 09 Out 2017 21:37

mal ae, xuxu! eu nao queria ofender! o/

xultz escreveu:Marcelo, vou tentar resumir prá você.

Eu fiz uma rotina usando bit field prá manipular os bits do registradores.
Depois eu fiz uma rotina que checa os bits usando máscara e operadores ternários, que é exatamente um if mas com aparência mais criptografada, mais difícil de entender e menos profissional.

Eu comparei o assembly gerado pelas duas rotinas. O compilador gerou um código exatamente igual, sem tirar nem por uma única instrução.

Neste caso, usando este compilador, para este PIC, não importa quão obscuro e criptografado você faça teu código C, o resultado do assembly será exatamente o mesmo (ou talvez pior). O código assembly que eu colei mostra o resultado, e em ambos casos eram exatamente iguais, e olhando ele, não consegui imaginar um código em assembly mais otimizado que aquele gerado.

Me fiz entender agora?
Avatar do usuário
msamsoniuk
Dword
 
Mensagens: 2935
Registrado em: 13 Out 2006 18:04


Voltar para PIC

Quem está online

Usuários navegando neste fórum: Nenhum usuário registrado e 1 visitante

x