Uso de recursos

Há quanto tempo!!! Estive sumido daqui porque não encontrei assuntos relevantes para postar e também porque tenho estudado algumas coisas interessantes. Eis-me aqui, digníssimos…

Quero falar sobre o problema do uso de recursos (memória, I/O, etc) e citar um exemplo simples que vi, recentemente… Hoje em dia os amigos “desenvolvedores” costumam pensar em termos de ambientes ideais (recursos virtualmente ilimitados). Aqueles que não o fazem pensam que o uso irrestrito de recursos é justificável graças ao preço do hardware (que caiu muito nas últimas décadas). É minha opinião que o pensamento deveria ser o contrário.

O “preço” alto que deve ser pago é o da baixa performance. Especialmente quando estamos falando de aplicações que serão usadas por centenas ou milhares de usuários simultaneamente. O cálculo é bem simples: Se sua aplicação usa dezenas de megabytes de memória, multiplicando isso pelo pior caso — suponha: uns 1000 usuários simultâneos — acabamos com o problema de uso de dezenas de gigabytes de recurso… Ao mesmo tempo, para cada usuário (ou um pequeno conjunto deles) uma thread é alocada para atendẽ-lo. No pior caso teríamos milhares de threads em execução em uma única CPU.

Isso demonstra o quão preciosos são os recursos. Minha aproximação ao problema é limitar a quantidade de memória física usada por thread e, na medida do possível, limitar a quantidade de threads em execução por core. Parece lógico, né? Mas não é o que se observa por ai. Em meu trabalho já topei com servidores rodando mais de 1000 threads simultâneas e, de maneira alguma, limitando o uso de memória. Sem contar com o consumo de handles para sockets! Não vou mostrar estatísticas ou códigos desses sistemas (já que, provavelmente eu iria pro olho-da-rua se o fizesse, mesmo que pudesse). Vou mostrar um exemplo aparentemente inofensivo que topei num código ‘livre’. Olhe só:

#define MAX_IP_ADDRS 16777216

int main(int argc, char **argv)
{
  //...
  static uint32_t addresses[MAX_IP_ADDRS];

  //...
  for (i = 0; i < MAX_IP_ADDRS; i++)
    addresses[i] = base + i;

  //...
  rand_addr = rand() % MAX_IP_ADDRS;
  use_addr(addresses[rand_addr]);

  //...
}

Repararam que o desenvolvedor alocou um buffer de 64 MB para armazenar valores sequenciais? A idéia aqui é “acelerar” a aplicação através do método conhecido como table lookup. Só que, declarar um vetor como static dentro de uma função faz com que o compilador gere código escondido para alocação dinâmica do vetor e o preenchimento do mesmo com zeros.

Isso pode te parecer exagero, afinal, o que são 64 MB hoje em dia? Acontece que o código original usa forking para duplicar o processo e, no processo, duplicar os recursos em uso. Usaríamos então 128 MB de memória do sistema com dois processos rodando (e se realizássemos mais forks, teríamos 64 MB/fork!).

Eis um código que faz a mesma coisa (e mais rápido) sem criar um grande array de 64 MB:

#define MAX_IP_ADDRS 16777216

int main(int argc, char **argv)
{
  //...
  rand_addr = rand() % MAX_IP_ADDRS;
  use_addr(base + rand_addr);

  //...
}

O conselho aqui é poupar, ao máximo, o uso de recursos e sempre revisar o seu código, porque sempre tem uma maneira mais eficiente e econômica de fazer alguma coisa.

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