L'approche de Google en matière d'optimisation des performances : techniques pratiques et réflexion d'ingénierie Ce guide technique classique, écrit par deux ingénieurs légendaires de Google, Jeff Dean et Sanjay Ghemawat, résume un ensemble de principes et de techniques spécifiques d'optimisation des performances, basés sur des années d'expérience pratique de Google dans la création de logiciels à hautes performances. Concept central : Réexamen de « l'optimisation prématurée » L'article commence par corriger un malentendu courant dans le secteur concernant la célèbre citation de Donald Knuth : « L'optimisation prématurée est la racine de tous les maux. » • Les 3 % critiques : L’intention initiale de Donald était de ne pas perdre de temps sur du code non critique, mais nous ne devons jamais renoncer à l’opportunité d’optimiser les 3 % critiques des chemins de code. • Culture de l'ingénierie : Dans les disciplines d'ingénierie matures, une amélioration des performances de 12 % représente un succès considérable et ne doit pas être considérée comme insignifiante. • Privilégiez l'efficacité : n'utilisez pas systématiquement ce principe comme excuse pour écrire du code inefficace. Lors de la programmation, privilégiez une alternative plus efficace sans pour autant augmenter significativement la complexité du code ni en réduire la lisibilité. Méthodologie : Estimation et mesure · Développer son intuition : Les ingénieurs d’excellence doivent être capables d’anticiper les problèmes sous-jacents. Il est essentiel de bien comprendre les processus complexes et chronophages des opérations informatiques de bas niveau. Cette intuition permet d’éliminer d’emblée les solutions de conception inefficaces. • La mesure est primordiale : ne devinez pas à l'aveuglette le goulot d'étranglement ; l'analyse des performances est l'outil principal. • Face à un profil « plat » : lorsqu’aucun point chaud n’apparaît clairement sur le graphique de performance, cela signifie que les optimisations les plus faciles à mettre en œuvre ont déjà été réalisées. Il convient alors de se concentrer sur l’accumulation de petites optimisations, l’ajustement des structures de boucles ou la reconstruction de l’algorithme à un niveau supérieur. L'article du guide technique pratique fournit de nombreux exemples de modifications de code spécifiques, couvrant principalement les dimensions suivantes : A. Mémoire et structures de données (C'est le cœur de l'optimisation) • Agencement compact : le cache est extrêmement précieux dans les processeurs modernes. Optimiser l’agencement de la mémoire, de sorte que les données fréquemment utilisées soient adjacentes en mémoire physique, peut réduire considérablement les défauts de cache. • Utilisez des index plutôt que des pointeurs : sur une machine 64 bits, un pointeur occupe 8 octets. Si possible, utilisez des index entiers plus petits, ce qui permet non seulement d’économiser de la mémoire, mais aussi de garantir la continuité des données. • Stockage aplati : évitez d’utiliser des conteneurs basés sur des nœuds (tels que std::map, std::list), car ils peuvent entraîner une fragmentation de la mémoire. Privilégiez les conteneurs à mémoire contiguë (tels que std::vector, absl::flat_hash_map). • Optimisation des petits objets : pour les collections qui contiennent généralement peu d’éléments, utilisez des conteneurs avec « stockage en ligne » (tels que absl::InlinedVector) pour éviter d’allouer de la mémoire sur le tas. B. Conception et utilisation de l'API • Interface de traitement par lots : Concevez une interface permettant le traitement simultané de plusieurs éléments. Cela réduit la surcharge liée aux appels de fonction et, plus important encore, amortit le coût d'acquisition des verrous. • Type de vue : Utilisez std::string_view ou absl::Span pour les paramètres de fonction chaque fois que cela est possible afin d’éviter les copies de données inutiles. C. Réduire l'allocation de mémoire. L'allocation de mémoire est coûteuse : elle consomme non seulement du temps d'allocation, mais elle nuit également à la localité du cache. • Réserver de l'espace : si vous connaissez approximativement la taille du vecteur, veillez à appeler d'abord la méthode .reserve() pour éviter les copies multiples dues au redimensionnement. • Réutilisation des objets : Dans une boucle, déplacez la déclaration des variables temporaires en dehors de la boucle afin d’éviter leur construction et destruction répétées. • Pool de mémoire Arena : Pour un ensemble complexe d’objets ayant un cycle de vie cohérent, l’allocateur Arena peut considérablement améliorer les performances et simplifier la destruction. D. Amélioration de l'algorithme : C'est l'arme ultime pour améliorer les performances. Optimiser un algorithme O(N²) en O(N log N) ou O(N) apporte des gains bien supérieurs à ceux d'un simple réglage fin du code. • Étude de cas : L’article démontre comment réduire considérablement la complexité temporelle en utilisant une simple recherche dans une table de hachage au lieu de l’opération d’intersection d’ensembles triés. E. Évitez les efforts inutiles : utilisez des chemins rapides : écrivez une logique de traitement dédiée aux scénarios les plus courants. Par exemple, lors du traitement de chaînes de caractères, si celles-ci sont composées uniquement de caractères ASCII, utilisez le chemin rapide pour éviter d’avoir recours à une logique de décodage UTF-8 complexe. Lire le texte original
Chargement du thread
Récupération des tweets originaux depuis X pour offrir une lecture épurée.
Cela ne prend généralement que quelques secondes.
