inserire una discussione prematura-discussione-è-la-radice-di-tutto-malvagio
Detto questo, ecco alcune abitudini che ho preso per evitare inutili efficienze e, in alcuni casi, per rendere il mio codice più semplice e anche più corretto.
Questa non è una discussione di principi generali, ma di alcune cose da tenere presenti per evitare di introdurre inefficienze inutili nel codice.
Conosci il tuo big-O
Questo dovrebbe probabilmente essere unito alla lunga discussione di cui sopra. È quasi logico che un loop all'interno di un loop, in cui il loop interno ripete un calcolo, sarà più lento. Per esempio:
for (i = 0; i < strlen(str); i++) {
...
}
Questo richiederà una quantità orribile di tempo se la stringa è davvero lunga, perché la lunghezza viene ricalcolata su ogni iterazione del ciclo. Si noti che GCC in realtà ottimizza questo caso perché strlen()
è contrassegnato come una funzione pura.
Quando si ordina un milione di numeri interi a 32 bit, l' ordinamento a bolle sarebbe il modo sbagliato di procedere . In generale, l'ordinamento può essere eseguito in tempo O (n * log n) (o meglio, nel caso di ordinamento radix), quindi a meno che tu non sappia che i tuoi dati saranno piccoli, cerca un algoritmo che sia almeno O (n * registro n).
Allo stesso modo, quando si ha a che fare con i database, prestare attenzione agli indici. Se tu SELECT * FROM people WHERE age = 20
e non hai un indice sulle persone (età), richiederà una scansione sequenziale O (n) piuttosto che una scansione dell'indice O (log n) molto più veloce.
Gerarchia aritmetica intera
Quando si programma in C, tenere presente che alcune operazioni aritmetiche sono più costose di altre. Per i numeri interi, la gerarchia va in questo modo (prima meno costosa):
Certo, il compilatore cose di solito ottimizzare come n / 2
per n >> 1
automaticamente se ci si rivolge un computer tradizionale, ma se ci si rivolge un dispositivo embedded, non si potrebbe ottenere questo lusso.
Inoltre, % 2
e & 1
hanno una semantica diversa. Divisione e modulo di solito arrotondano verso zero, ma la sua implementazione è definita. Buon vecchio >>
e &
ruota sempre verso l'infinito negativo, che (secondo me) ha molto più senso. Ad esempio, sul mio computer:
printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1
Quindi, usa ciò che ha senso. Non pensare di essere un bravo ragazzo usando % 2
quando stavi per scrivere & 1
.
Operazioni in virgola mobile costose
Evita pesanti operazioni in virgola mobile come pow()
e log()
nel codice che non ne ha davvero bisogno, specialmente quando si tratta di numeri interi. Prendi, ad esempio, la lettura di un numero:
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;
}
Non solo questo uso di pow()
(e le int
<-> double
conversioni necessarie per usarlo) è piuttosto costoso, ma crea un'opportunità per la perdita di precisione (per inciso, il codice sopra non ha problemi di precisione). Ecco perché sussulto quando vedo questo tipo di funzione usata in un contesto non matematico.
Inoltre, nota come l'algoritmo "intelligente" di seguito, che si moltiplica per 10 su ogni iterazione, è in realtà più conciso del codice sopra:
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;
}