4 May
Enfim chegamos à parte que todos esperavam. Como extrair ou últimos ciclos do seu processador. Como evitar fazer qualquer coisa que não seja realmente necessária ao seu código. Mas antes de começar a carnificina, algumas considerações:
Mas, se depois de fazer tudo isso, seu código ainda não roda em tempo satisfatório, o resto das dicas pode ajudar bastante:
Mais informações: Optimizing C and Cpp e C Coding
Próximo post: ???
Posts Relacionados:
Acompanhe-nos por
RSS, por Email ou via Twitter.
Veja como ter um desconto no Dreamhost: um excelente servidor web.
27 Apr
A construção de algoritmos genéricos é útil pois evita que você tenha que reescrever código muito semelhante para trabalhar com dados diferentes. Bons exemplos de algoritmos genéricos são o qsort e bsearch, ambos da stdlib.h . Eles são implementações de um algoritmo de ordenação e de busca em vetores de qualquer tipo de dado. Eles funcionam mais ou menos assim:
- Além das informações óbvias, como o vetor a ser ordenado (base) ou item a ser procurado (key) e o tamanho deste vetor (nmemb), eles também pedem que você passe o tamanho em bytes de um item do vetor (size) e uma função de comparação para esses itens (comp). É aqui que começa a parte genérica do algorítmo.
Basicamente, onde numa implementação normal desses algoritmos você colocaria algum operador de comparação, você agora vai colocar uma chamada para a função de comparação. E onde estaria um operador de atribuição, você vai colocar, por exemplo, um memcpy copiando “size” bytes de uma posição para a outra. Dessa forma, seu algorítmo agora é capaz de funcionar para qualquer tipo de dado que você deseje usar. Mas qual a vantagem? Existem várias:
O único problema disso é que o código fica meio “hard coded” e você precisa trabalhar com ponteiros de função que são feios. Mas isso você resolve criando uma camada a mais que esconde toda essa parte. Da até pra esconder mais coisas se você usar macros, mas isso é assunto de um próximo post.
Antes de implementar um algorítmo genérico, recomendo que olhem os posts sobre Ponteiros e Ponteiros de Função pois a base para os algorítmos genéricos são eles.
Mais informações: Boa pergunta… Nunca encontrei nada sobre isso. Deixem um comentário ou mandem um e-mail pra mim.
Próximo post: Tunning
Posts Relacionados:
Acompanhe-nos por
RSS, por Email ou via Twitter.
Veja como ter um desconto no Dreamhost: um excelente servidor web.
23 Mar
Enfim chegamos ao assunto que causa dor de cabeça à maioria dos programadores iniciantes (e à muitos veteranos também). É complicado trabalhar com a referência ao invés de trabalhar com o objeto.
Basicamente, um ponteiro é a posição de memória onde está a estrutura. Se pensarmos na memória como um vetor, o ponteiro é um índice desse vetor. Em C, um ponteiro simplesmente indica uma posição de memória e nada mais. Todo o resto da informação (como, por exemplo, que tipo de estrutura é apontada) existe apenas em tempo de compilação. Em tempo de execução memória é simplesmente memória e nada mais. Isso permite um maior controle sobre ela. É possível com um simples cast mudar totalmente a forma como ela é tratada. Por exemplo, é possível transformar um vetor de inteiros em um vetor de char. Internamente a única coisa que mudou é quantos bytes tem a estrutura do vetor. Isso também vale para casts de ponteiro de estruturas.
Tá, mas o que eu faço com todas essas mudanças? Simples. Sabendo de tudo isso é possível agora aproveitar-se disso. Por exemplo, você pode fazer uma alocação de centenas de bytes e utilizá-la como vários objetos diferentes em sequência, dessa forma criando um semi-alocador de memória muito mais rápido do que o malloc (o custo do malloc ficará dividido entre os vários objetos). Mas para isso é necessário entender um pouco de aritmética de ponteiros.
Quando você soma 1 a um inteiro, ele simplesmente incrementa o inteiro. Se ele valia 2 passa a valer 3. Com ponteiros é um pouco diferente. Cada vez que você incrementa um ponteiro a seguinte conta é feita (sendo aux o ponteiro e código aux++):
aux += sizeof(*aux)
Ou seja, quando você incrementa o ponteiro ele na verdade é incrementado com o samanho do objeto para o qual ele aponta, em bytes. Vale lembrar que é o mesmo cálculo realizado para acessar a posição de um vetor (esse cálculo é invisível pois você usa [ e ]). Se você usar soma normal, ele fará algo assim:
aux += N*sizeof(*aux)
Com N sendo o número que foi somado ao ponteiro.
Com tudo isso, é possível fazer algumas “bizarrices” não recomendadas, mas que funcionam muito bem se você vai produzir algum software pequeno e que você consegue entender de ponta a ponta:
Próxima semana: Algoritmos genéricos em C
Posts Relacionados:
Acompanhe-nos por
RSS, por Email ou via Twitter.
Veja como ter um desconto no Dreamhost: um excelente servidor web.
16 Mar
Para deixar um sistema mais rápido (e muitas vezes mais “macio”) é necessário que você execute várias coisas ao mesmo tempo. Você não precisa fazer com que seu programa simplesmente pare de fazer algo simplesmente porque o usuário clicou em outro botão. Você também pode estar querendo aproveitar um segundo processador da máquina ou mesmo aumentar o número de acessos ao processador (é difícil fazer um jogo rodar quando o Windows come metade do sistema e o anti-vírus mastiga o resto). O que fazer então? Bom, você poderia pedir pro usuário fechar o anti-vírus (o que ele não vai fazer) ou mandar ele apelar pro bom senso e desligar o tocador de mp3, Word, Messenger, Internet Explorer e todas as tranqueiras que ele tem em stand-by para então rodar o seu programa. Esquece… Isso não vai acontecer. O que nós programadores podemos fazer, então? Simples. Podemos dividir o nosso programa em várias threads e aumentar a prioridade delas, aumentando o nosso acesso ao(s) processador(es) e diminuindo o dos outros programas. Mas vamos com calma.
Uma thread é basicamente um outro programa rodando mas que tem os mesmos direitos de acesso à memória que o programa que carrega ela (que seria a thread principal). Esta não é a definição formal. É mais uma utilidade prática do negócio. Como ele tem os mesmos direitos de acesso à memória, compartilhar dados entre os dois programas é fácil. E cada um tem sua própria pilha, então nem precisa se preocupar com um eventual estouro dela (não abusem dessa frase, é possível estourar a pilha, apenas menos provável).
C não possui nenhum suporte nativo à threads (isso é algo que depende muito do kernel do sistema operacional, pois é ele quem coordena os processos - entenda como threads + programas - e verifica quando estes vão pro processador e para qual processador vão). Então é necessário que você utilize uma biblioteca do sistema operacional.
O Windows utiliza o header windows.h (sugestivo, não?), mas qualquer coisa que você compile que tenha incluído esse header irá demorar milênios para compilar, pois esse header é realmente imenso. Para evitar isso, defina a constante WIN32_LEAN_AND_MEAN antes de incluir o header e assim você evitará incluir um monte de coisas nas quais você provavelmente não está interessado.
Linux e MacOS X utilizam a pthread.h. MacOS Clássico não possui suporte nativo, mas difícilmente você irá produzir uma aplicação pra clássico hoje em dia.
Mas e se eu quiser produzir uma aplicação portável entre alguns desses sistemas, o que que eu faço? Vou ter que forçar a portabilidade “no braço” com #ifdef ? Não. Você pode usar uma das seguintes bibliotecas:
Mas antes que você pense que threads são a solução para todos os problemas, tome cuidado. Thread é uma ferramenta poderosa, mas é bem complicado de utilizá-la. Recomendo que estude seriamente programação concorrente antes de se aventurar pelos incontáveis erros de sincronismo praticamente indetectáveis que as threads produzem.
Próxima semana: Ponteiros e Aritmética de Ponteiros.
Posts Relacionados:
Acompanhe-nos por
RSS, por Email ou via Twitter.
Veja como ter um desconto no Dreamhost: um excelente servidor web.