Arquivo para 29 de junho de 2012

Otimização 0.1

Achei bem interessante que nesse tempo que fiquei sem escrever, o blog teve uma quantidade de acessos maior que em tempos de atividade. De qualquer forma, um post de vez em quando não faz mal a ninguém :).

Tem um site de tirinhas chamado Vida de Programador que posta situações corriqueiras (as vezes) de forma engraçada.

Na tirinha de hoje, ele mostrava o código de um programador iniciante:

bool isValid;
...
switch(isValid) {
case true: ...
case false: ...
}

Claro que não faz sentido tanta verborragia pra algo tão simples como uma variável booleana. No entanto, as pessoas se esquecem que os compiladores atuais são muito bons e não vão deixar seu código lento só porque você preferiu que ele fosse “mais legível”.

Uma excelente ferramenta online chamada GCC Explorer mostra a saída assembly do seu compilador preferido com diversos níveis de otimização.

Independente do nível de otimização, o código gerado para o trecho acima será extamente o mesmo que para “if...else” ou o operador ternário “? :“.

int testFunction(bool x) {
  switch (x) {
    case true:
        return 10;
        break;
    case false:
        return 12;
  }
}

int testFunction2(bool x) {
  return x? 10 : 12;
}

E o Assembly gerado (com -O2) será:

testFunction(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret
testFunction2(bool):
	cmpb	$1, %dil
	sbbl	%eax, %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Se você olhar, não há nenhum jump no assembly gerado. Ele compara a variável com o número 1, subtrai o registro de retorno dele mesmo com borrow, faz um AND com o 2 e depois soma 10. Você não ia pensar que retornar 12 ou 10 significa colocar 2 ou 0 numa variável e depois somar 10, mas o compilador sabe que lógica AND e OR para números inteiros é muito mais rápida que jumps e faz tudo sem que você se preocupe. Esse código seria semelhante a:

int testFunction3(bool x) {
  return 10 + (2 & -(1-x));
}

Mas o GCC consegue diminuir ainda mais o assembly!

testFunction3(bool):
	leal	3(%rdi), %eax
	andl	$2, %eax
	addl	$10, %eax
	ret

Só que ninguém vai querer ler um código tão feio assim que apenas poupa 1 nanossegundo.

Sem dar razão à pessoa que fez um switch para um bool, se o código não é o gargalo do seu programa, não há motivo para mudar. Como escreveu Donald Knuth: otimização prematura é a raiz de todo o mal.