Optimisations génériques
Voici quelques-unes de mes optimisations préférées. J'ai en fait augmenté les temps d'exécution et réduit la taille des programmes en les utilisant.
Déclarer de petites fonctions en tant que inline
macros ou
Chaque appel à une fonction (ou méthode) entraîne une surcharge, telle que le transfert de variables sur la pile. Certaines fonctions peuvent également entraîner des frais généraux au retour. Une fonction ou une méthode inefficace a moins d'instructions dans son contenu que la surcharge combinée. Ce sont de bons candidats pour l'inlining, que ce soit sous forme de #define
macros ou de inline
fonctions. (Oui, je sais que ce inline
n'est qu'une suggestion, mais dans ce cas, je le considère comme un rappel au compilateur.)
Supprimer le code mort et redondant
Si le code n'est pas utilisé ou ne contribue pas au résultat du programme, supprimez-le.
Simplifiez la conception des algorithmes
Une fois, j'ai supprimé beaucoup de code d'assemblage et de temps d'exécution d'un programme en écrivant l'équation algébrique qu'il calculait, puis j'ai simplifié l'expression algébrique. La mise en œuvre de l'expression algébrique simplifiée a pris moins de place et de temps que la fonction d'origine.
Déroulement de la boucle
Chaque boucle a une surcharge d'incrémentation et de vérification de terminaison. Pour obtenir une estimation du facteur de performance, comptez le nombre d'instructions dans la surcharge (minimum 3: incrémenter, vérifier, aller au début de la boucle) et diviser par le nombre d'instructions à l'intérieur de la boucle. Plus le nombre est bas, mieux c'est.
Edit: fournissez un exemple de déroulement de boucle Avant:
unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Après déroulement:
unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
sum += *buffer++; // 1
sum += *buffer++; // 2
sum += *buffer++; // 3
sum += *buffer++; // 4
sum += *buffer++; // 5
sum += *buffer++; // 6
sum += *buffer++; // 7
sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
sum += *buffer++;
}
Dans cet avantage, un avantage secondaire est obtenu: davantage d'instructions sont exécutées avant que le processeur ne doive recharger le cache d'instructions.
J'ai eu des résultats étonnants lorsque j'ai déroulé une boucle à 32 instructions. C'était l'un des goulots d'étranglement puisque le programme devait calculer une somme de contrôle sur un fichier de 2 Go. Cette optimisation combinée à la lecture de bloc a amélioré les performances de 1 heure à 5 minutes. Le déroulement en boucle offrait également d'excellentes performances en langage d'assemblage, mon memcpy
était beaucoup plus rapide que celui du compilateur memcpy
. - TM
Réduction des if
déclarations
Les processeurs détestent les branches, ou les sauts, car cela oblige le processeur à recharger sa file d’instructions.
Arithmétique booléenne ( modifié: format de code appliqué au fragment de code, exemple ajouté)
Convertissez les if
instructions en affectations booléennes. Certains processeurs peuvent exécuter des instructions de manière conditionnelle sans branchement:
bool status = true;
status = status && /* first test */;
status = status && /* second test */;
Le court-circuit de l' opérateur ET logique ( &&
) empêche l'exécution des tests si le status
est false
.
Exemple:
struct Reader_Interface
{
virtual bool write(unsigned int value) = 0;
};
struct Rectangle
{
unsigned int origin_x;
unsigned int origin_y;
unsigned int height;
unsigned int width;
bool write(Reader_Interface * p_reader)
{
bool status = false;
if (p_reader)
{
status = p_reader->write(origin_x);
status = status && p_reader->write(origin_y);
status = status && p_reader->write(height);
status = status && p_reader->write(width);
}
return status;
};
Allocation de variable de facteur en dehors des boucles
Si une variable est créée à la volée dans une boucle, déplacez la création / allocation avant la boucle. Dans la plupart des cas, la variable n'a pas besoin d'être allouée à chaque itération.
Factoriser les expressions constantes en dehors des boucles
Si un calcul ou une valeur de variable ne dépend pas de l'index de la boucle, déplacez-le en dehors (avant) de la boucle.
E / S par blocs
Lisez et écrivez des données en gros morceaux (blocs). Le plus gros le meilleur. Par exemple, lire un octect à la fois est moins efficace que lire 1024 octets avec une seule lecture.
Exemple:
static const char Menu_Text[] = "\n"
"1) Print\n"
"2) Insert new customer\n"
"3) Destroy\n"
"4) Launch Nasal Demons\n"
"Enter selection: ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);
L'efficacité de cette technique peut être démontrée visuellement. :-)
N'utilisez pas la printf
famille pour des données constantes
Les données constantes peuvent être sorties en utilisant une écriture de bloc. L'écriture formatée perdra du temps à analyser le texte pour le formatage des caractères ou le traitement des commandes de formatage. Voir l'exemple de code ci-dessus.
Formater en mémoire, puis écrire
Formatez en un char
tableau en utilisant plusieurs sprintf
, puis utilisez fwrite
. Cela permet également de diviser la présentation des données en «sections constantes» et sections variables. Pensez au publipostage .
Déclarez le texte constant (littéraux de chaîne) comme static const
Lorsque des variables sont déclarées sans le static
, certains compilateurs peuvent allouer de l'espace sur la pile et copier les données de la ROM. Ce sont deux opérations inutiles. Cela peut être résolu en utilisant le static
préfixe.
Enfin, le code comme le compilateur le ferait
Parfois, le compilateur peut optimiser plusieurs petites instructions mieux qu'une version compliquée. En outre, l'écriture de code pour aider le compilateur à optimiser est également utile. Si je veux que le compilateur utilise des instructions spéciales de transfert de bloc, j'écrirai du code qui semble devoir utiliser les instructions spéciales.