insérer une discussion prématurée est la racine de tous les maux
Cela dit, voici quelques habitudes que j'ai adoptées pour éviter une efficacité inutile et, dans certains cas, rendre mon code plus simple et plus correct également.
Ce n'est pas une discussion des principes généraux, mais de certaines choses à prendre en compte pour éviter d'introduire des inefficacités inutiles dans le code.
Connaissez votre big-O
Cela devrait probablement être fusionné dans la longue discussion ci-dessus. C'est à peu près le bon sens qu'une boucle à l'intérieur d'une boucle, où la boucle intérieure répète un calcul, va être plus lente. Par exemple:
for (i = 0; i < strlen(str); i++) {
...
}
Cela prendra un temps horrible si la chaîne est vraiment longue, car la longueur est recalculée à chaque itération de la boucle. Notez que GCC optimise réellement ce cas car il strlen()
est marqué comme une fonction pure.
Lors du tri d'un million d'entiers 32 bits, le tri à bulles serait la mauvaise façon de procéder . En général, le tri peut être effectué en temps O (n * log n) (ou mieux, dans le cas du tri radix), donc à moins que vous ne sachiez que vos données seront petites, recherchez un algorithme qui est au moins O (n * log n).
De même, lorsque vous traitez avec des bases de données, faites attention aux index. Si vous SELECT * FROM people WHERE age = 20
, et que vous n'avez pas d'index sur les personnes (âge), cela nécessitera un scan séquentiel O (n) plutôt qu'un scan d'index O (log n) beaucoup plus rapide.
Hiérarchie arithmétique entière
Lors de la programmation en C, gardez à l'esprit que certaines opérations arithmétiques sont plus chères que d'autres. Pour les entiers, la hiérarchie se présente comme suit (la moins chère en premier):
Certes, le compilateur généralement des choses comme Optimize n / 2
à n >> 1
automatiquement si vous ciblez un ordinateur grand public, mais si vous ciblez un appareil embarqué, vous pourriez ne pas obtenir ce luxe.
Aussi, % 2
et & 1
ont une sémantique différente. La division et le module arrondissent généralement vers zéro, mais leur implémentation est définie. Bon vieux >>
et &
toujours arrondi vers l'infini négatif, ce qui (à mon avis) a beaucoup plus de sens. Par exemple, sur mon ordinateur:
printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1
Par conséquent, utilisez ce qui a du sens. Ne pensez pas que vous êtes un bon garçon en utilisant % 2
quand vous alliez à l'origine écrire & 1
.
Opérations en virgule flottante coûteuses
Évitez les opérations lourdes en virgule flottante comme pow()
et log()
dans le code qui n'en a pas vraiment besoin, en particulier lorsqu'il s'agit d'entiers. Prenons, par exemple, la lecture d'un nombre:
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 seulement cette utilisation de pow()
(et les conversions int
<-> double
nécessaires pour l'utiliser) est assez chère, mais elle crée une opportunité de perte de précision (incidemment, le code ci-dessus n'a pas de problèmes de précision). C'est pourquoi je grimace quand je vois ce type de fonction utilisé dans un contexte non mathématique.
Notez également que l'algorithme "intelligent" ci-dessous, qui se multiplie par 10 à chaque itération, est en fait plus concis que le code ci-dessus:
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;
}