Propagação de ponteiros versus estruturas globais

Algumas pessoas me perguntam porque diabos modifiquei o código original do T50 (o código atual pode ser obtido aqui), onde o acesso às variáveis da estrutura de configuração era feito diretamente na variável global e usei a propagação do ponteiro para a estrutura em diversas funções do código… Bem, os motivos são dois: Performance e tamanho do código.

Considere isso:

#include <stdio.h>

struct config {
  int a;
  int b;
} co = {};

void f(int x)
{
  if (co.b == x)
    puts("ok");
}

void g(struct config *co, int x)
{
  if (co->b == x)
    puts("ok");
}

Aparentemente ambos os cóigos fazem a mesmíssima coisa, não é? Acontece que a função f precisa calcular o endereço efetivo da variável global co e, para isso, precisa usar um deslocamento, dentro do segmento .data. Já a função g não precisa fazer isso, uma vez que o ponteiro para a estrutura (que convenientemente chamei de co também!) é fornecido no registrador RDI (no modo x86-64). Eis o código em assembly (com os uops):

f:
   0:   39 3d 00 00 00 00               cmp    DWORD PTR [rip+0x0],edi
   6:   74 08                           je     10 <f+0x10>
   8:   c3                              ret    
   9:   0f 1f 80 00 00 00 00            nop    DWORD PTR [rax+0x0]
  10:   48 bf 00 00 00 00 00 00 00 00   mov    rdi,0x0
  1a:   e9 00 00 00 00                  jmp    puts
  1f:   90                              nop

g:
  20:   39 77 04             	        cmp    DWORD PTR [rdi+0x4],esi
  23:   74 0b                	        je     30 <g+0x10>
  25:   c3                   	        ret    
  26:   66 2e 0f 1f 84 00 00 00 00 00   nop    WORD PTR cs:[rax+rax*1+0x0]
  30:   48 bf 00 00 00 00 00 00 00 00   mov    rdi,0x0
  3a:   e9 00 00 00 00       	        jmp    puts

Os bytes marcados em vermelho correspondem aos que serão substituídos pelo linker com o deslocamento de co.b em relação ao início do segmento .data. Note que eles não estão contidos na função g, que calcula o endereço de b, na estrutura co, através do ponteiro contido em RDI, simplesmente adicionando uma constante ao ponteiro. Em ambos os casos o endereço efetivo terá que ser calculado, mas na função g isso é mais eficiente.

Os bytes mostrados em azul serão substituídos pelo endereço da string “Ok” (no segmento .rodata, provavelmente) e os bytes marcados em verde pelo endereço (relativo) da função puts.

Repare que a função g tem o mesmo tamanho da função f, mas só porque ela precisa de um alinhamento maior (note o hint-nop nas funções). Desconsiderando o alinhamento, a função g é menor que f e, graças a 3 bytes a menos no acesso ao membro b da estrutura, ela será, pelo menos, 1 ciclo de máquina mais rápida. Ahhhh… Por que desse alinhamento? É que a Intel recomenda que saltos sejam feitos para endereços alinhados de 16 em 16 bytes (parágrafos) e o GCC tenta obedecer essa regra sempre que pode.

De forma geral, propagar ponteiros ao invés de usar referências globais pode ser muito interessante, tanto em relação à performance quanto ao tamanho final do código.

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s