inserir palestra prematura-discussão-é-a-raiz-de-todo-mal
Dito isso, aqui estão alguns hábitos que eu entrei para evitar eficiência desnecessária e, em alguns casos, tornem meu código mais simples e mais correto também.
Esta não é uma discussão de princípios gerais, mas de algumas coisas a serem observadas para evitar a introdução de ineficiências desnecessárias no código.
Conheça o seu big-O
Provavelmente, isso deve ser mesclado na longa discussão acima. É praticamente senso comum que um loop dentro de um loop, onde o loop interno repita um cálculo, seja mais lento. Por exemplo:
for (i = 0; i < strlen(str); i++) {
...
}
Isso levará uma quantidade enorme de tempo se a string for realmente longa, porque o comprimento está sendo recalculado a cada iteração do loop. Observe que o GCC realmente otimiza esse caso porque strlen()
está marcado como uma função pura.
Ao classificar um milhão de números inteiros de 32 bits, a classificação por bolha seria o caminho errado . Em geral, a classificação pode ser feita no tempo O (n * log n) (ou melhor, no caso da classificação radix); portanto, a menos que você saiba que seus dados serão pequenos, procure um algoritmo que seja pelo menos O (n * log n).
Da mesma forma, ao lidar com bancos de dados, esteja ciente dos índices. Se você SELECT * FROM people WHERE age = 20
e você não tiver um índice de pessoas (idade), será necessária uma varredura sequencial O (n) em vez de uma varredura muito mais rápida do índice O (log n).
Hierarquia aritmética inteira
Ao programar em C, lembre-se de que algumas operações aritméticas são mais caras que outras. Para números inteiros, a hierarquia é mais ou menos assim (menos cara primeiro):
Concedido, o compilador coisas normalmente otimizar como n / 2
para n >> 1
automaticamente se você está alvejando um computador mainstream, mas se você está alvejando um dispositivo embutido, você não pode ter esse luxo.
Além disso, % 2
e & 1
tem semântica diferente. A divisão e o módulo geralmente arredondam para zero, mas sua implementação é definida. O bom e velho >>
e &
sempre rodadas em direção ao infinito negativo, o que (na minha opinião) faz muito mais sentido. Por exemplo, no meu computador:
printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1
Portanto, use o que faz sentido. Não pense que você está sendo um bom garoto usando % 2
quando estava originalmente escrevendo & 1
.
Operações caras de ponto flutuante
Evite operações pesadas de ponto flutuante como pow()
e log()
no código que realmente não precisa delas, especialmente ao lidar com números inteiros. Veja, por exemplo, a leitura de um número:
int parseInt(const char *str)
{
const char *p;
int digits;
int number;
int position;
// Count the number of digits
for (p = str; isdigit(*p); p++)
{}
digits = p - str;
// Sum the digits, multiplying them by their respective power of 10.
number = 0;
position = digits - 1;
for (p = str; isdigit(*p); p++, position--)
number += (*p - '0') * pow(10, position);
return number;
}
Não é apenas esse uso pow()
(e as int
<-> double
conversões necessárias para usá-lo) bastante caro, como também cria uma oportunidade para perda de precisão (aliás, o código acima não possui problemas de precisão). É por isso que estremeço quando vejo esse tipo de função usada em um contexto não matemático.
Além disso, observe como o algoritmo "inteligente" abaixo, que se multiplica por 10 em cada iteração, é realmente mais conciso do que o código acima:
int parseInt(const char *str)
{
const char *p;
int number;
number = 0;
for (p = str; isdigit(*p); p++) {
number *= 10;
number += *p - '0';
}
return number;
}