Vous devez savoir où mettre le zéro



Certaines optimisations nécessitent des structures de données complexes et des milliers de lignes de code. Dans d'autres cas, une augmentation sérieuse de la productivité donne un changement minimal: il suffit parfois de mettre à zéro. C'est comme un vieux conte sur un skipper qui connaît le bon endroit pour frapper avec un marteau, puis facture au client: 0,50 $ pour un coup à la valve et 999,50 $ pour savoir où frapper.

J'ai personnellement rencontré plusieurs erreurs de performance qui ont été corrigées en entrant un zéro, et dans cet article, je veux partager deux histoires.

L'importance de la mesure


À l'époque de la Xbox d'origine, j'ai aidé à optimiser de nombreux jeux. Dans l'un d'eux, le profileur a souligné la fonction de transformation de la matrice, qui a consommé 7% du temps CPU - le plus grand saut sur le graphique. J'ai donc diligemment mis au point l'optimisation de cette fonctionnalité.

On peut voir que je n'étais pas le premier à essayer de le faire. La fonction a déjà été réécrite dans l'assembleur. J'ai trouvé plusieurs améliorations potentielles dans le langage d'assemblage et j'ai essayé de mesurer leur effet. C'est une étape importante, sinon il est facile de faire de l '«optimisation», qui ne changera rien ni même aggravera la situation.

Cependant, la mesure était difficile. J'ai couru le jeu, joué un peu avec le profilage parallèle, puis étudié le profil: le code est-il devenu plus rapide? Il semblait qu'il y avait une légère amélioration, mais il était impossible de dire avec certitude.

J'ai donc appliqué la méthode scientifique . Il a écrit une collection de tests pour gérer les anciennes et les nouvelles versions de code afin de mesurer avec précision les différences de performances. Cela n'a pas pris beaucoup de temps: comme prévu, le nouveau code était environ 10% plus rapide que l'ancien.

Mais il s'est avéré qu'une accélération de 10% est un non-sens.

Il est beaucoup plus intéressant de noter que l'intérieur du code de test a été exécuté environ 10 fois plus rapidement que dans le jeu. Ce fut une découverte passionnante.

Après avoir vérifié les résultats, j'ai regardé dans le vide pendant un certain temps, mais ensuite j'ai réalisé.

Rôle de mise en cache


Pour donner aux développeurs de jeux un contrôle total et des performances maximales, les consoles de jeux vous permettent d'allouer de la mémoire avec différents attributs. En particulier, la Xbox d'origine vous permet d'allouer de la mémoire non cache. Ce type de mémoire (en fait, le type de balise dans les tableaux de pages) est utile lors de l'écriture de données sur le GPU. Étant donné que la mémoire n'est pas mise en cache, l'écriture ira dans la RAM presque immédiatement sans aucun retard ni contamination du cache avec un mappage «normal».

Ainsi, la mémoire non mise en cache est une optimisation importante, mais elle doit être utilisée avec précaution. En particulier, il est extrêmement important que les jeux n'essaient jamais de lire à partir de la mémoire non mise en cache, sinon leurs performances diminueront sérieusement. Même le processeur 733 MHz relativement lent de la Xbox d'origine a besoin de ses propres caches pour fournir des performances de lecture suffisantes.

Maintenant, il devient clair ce qui se passe. Apparemment, pour cette fonction, les données sont allouées dans la mémoire non mise en cache, d'où de faibles performances. Un petit test a confirmé cette hypothèse, il est donc temps de résoudre le problème. J'ai trouvé la ligne où la mémoire est allouée, j'ai double-cliqué sur la valeur de l'indicateur et pointé vers zéro.

Au lieu d'environ 7% du temps du processeur, la fonction a commencé à consommer environ 0,7% et n'était plus un problème.

À la fin de la semaine, mon rapport ressemblait à ceci: «39 999 heures de recherche, 0,001 heure de programmation est un énorme succès!»

Les développeurs n'ont généralement pas à se soucier de l'allocation accidentelle de mémoire non mise en cache: sur la plupart des systèmes d'exploitation, cette option n'est pas disponible dans l'espace utilisateur à l'aide de méthodes standard. Mais si vous êtes curieux de savoir combien de mémoire non mise en cache peut ralentir le programme, essayez les indicateurs PAGE_NOCACHE ou PAGE_WRITECOMBINE dans VirtualAlloc .

0 Gio vaut mieux que 4 Gio


Je veux vous raconter une autre histoire. Il s'agit d'un bug que j'ai trouvé, et quelqu'un d'autre l'a corrigé. Il y a quelques années, j'ai remarqué que le cache disque de mon ordinateur portable était trop souvent effacé. J'ai suivi que cela se produit lorsque la ligne 4 Gio est atteinte, et à la fin, il s'est avéré que le pilote de mon nouveau disque dur de sauvegarde définit SectorSize sur 0xFFFFFFFF (ou −1) lorsqu'il pointe vers une taille de secteur inconnue. Le noyau Windows interprète cette valeur comme 4 Gio et alloue le bloc de mémoire correspondant, ce qui a provoqué le problème.

Je n'ai pas de contacts dans Western Digital, mais je peux supposer qu'ils ont corrigé cette erreur en remplaçant la constante 0xFFFFFFFF (ou −1) par zéro. Un personnage est entré - et a résolu un grave problème de performances.

(En savoir plus sur cette étude dans l'article «Ralentissement de Windows: exploration et identification» )

Observations


  • Dans les deux cas, le problème vient de la mise en cache
  • L'utilisation d'un profileur a été décisive pour identifier le problème.
  • Si le patch n'est pas vérifié par des mesures, cela n'aidera pas nécessairement.
  • Je pourrais écrire sur de nombreux autres cas de ce genre, mais ils sont soit trop secrets, soit trop ennuyeux.
  • La bonne décision ne doit pas être compliquée. Parfois, une énorme amélioration donne un petit changement. Tout ce que vous devez savoir, c'est où

Il m'est arrivé d'optimiser le code en décommentant #define et par d'autres changements triviaux. Dites-nous dans les commentaires si vous avez de telles histoires.

Source: https://habr.com/ru/post/fr434056/


All Articles