N'importe qui peut faire une erreur Rockstar (et moi aussi.)



Il y a quelques mois, un article étonnant [traductions en Habré : un et deux ] sur Grand Theft Auto Online a fait la une des journaux .



Je vous conseille de lire l'intégralité de l'article, mais en bref, GTA Online a soudainement eu des performances quadratiques lors de l'analyse d'un gros blob JSON (en raison de plusieurs appels strlen



) ; après avoir corrigé cette erreur, le temps de chargement a diminué de près de 70 %.



Cela a suscité un vif débat : est-ce que C est à blâmer pour cela ? Ou peut-être "web shit" ? Ou le capitalisme et ses incitations ?



Cependant, tout le monde était d'accord sur une chose : ils n'auraient jamais écrit de telles bêtises.



( Pouvez-vous déjà sentir ce qui s'en vient ? )




L'un de mes projets parallèles est une visionneuse de modèles 3D haute performance appelée Erizo .





Avec un code intelligent, il ouvre un binaire STL de 97 Mo sur un Macbook Pro 2013 en seulement 165 millisecondes. C'est une vitesse incroyable .



Pour des raisons de compatibilité, j'ai également écrit un petit parseur pour ASCII STL .



ASCII STL est un format de texte brut mal spécifié qui ressemble à ceci :



solid cube_corner
          facet normal 0.0 -1.0 0.0
            outer loop
              vertex 0.0 0.0 0.0
              vertex 1.0 0.0 0.0
              vertex 0.0 0.0 1.0
            endloop
          endfacet
          facet normal 0.0 0.0 -1.0
            outer loop
              vertex 0.0 0.0 0.0
              vertex 0.0 1.0 0.0
              vertex 1.0 0.0 0.0
            endloop
          endfacet
          ...
endsolid
      
      





J'ai écrit un analyseur extrêmement robuste avec un commentaire comme celui-ci :



/*     ASCII STL:  , 
 *   'vertex',         float. */
      
      





Le chargement de la STL ASCII semblait toujours un peu lent, mais j'ai supposé que cela était dû à un format de texte inefficace.



(Les nuages ​​se rassemblent. )






Plusieurs événements se sont produits en quelques jours :



  • Pour la première fois depuis plusieurs années, je suis revenu à l'ancien code Erizo pour corriger l' erreur de mise au point sur macOS
  • Publication d'un article sur GTA Online
  • De la discussion qui a suivi, j'ai appris que - sscanf



  • J'ai remarqué que le chargement ASCII STL était très lent .


Voici les journaux de téléchargement de la STL ASCII de 1,5 Mo horodatée (en secondes) :



[erizo] (0.000000) main.c:10      | Startup!
[erizo] (0.162895) window.c:91    | Created window
[erizo] (0.162900) window.c:95    | Made context current
[erizo] (0.168715) window.c:103   | Initialized GLEW
[erizo] (0.178329) window.c:91    | Created window
[erizo] (0.178333) window.c:95    | Made context current
[erizo] (1.818734) loader.c:109   | Parsed ASCII STL
[erizo] (1.819471) loader.c:227   | Workers have deduplicated vertices
[erizo] (1.819480) loader.c:237   | Got 5146 vertices (7982 triangles)
[erizo] (1.819530) loader.c:240   | Waiting for buffer...
[erizo] (1.819624) loader.c:326   | Allocated buffer
[erizo] (1.819691) loader.c:253   | Sent buffers to worker threads
[erizo] (1.819883) loader.c:258   | Joined worker threads
[erizo] (1.819887) loader.c:279   | Loader thread done
[erizo] (1.821291) instance.c:32  | Showed window
      
      





Plus de 1,8 secondes se sont écoulées du moment du lancement à l'affichage de la fenêtre !



En jetant un nouveau regard sur l'analyseur ASCII, j'ai vu que la raison est évidente :



    /*  The most liberal ASCII STL parser:  Ignore everything except
     *  the word 'vertex', then read three floats after each one. */
    const char VERTEX_STR[] = "vertex ";
    while (1) {
        data = strstr(data, VERTEX_STR);
        if (!data) {
            break;
        }

        /* Skip to the first character after 'vertex' */
        data += strlen(VERTEX_STR);

        for (unsigned i=0; i < 3; ++i) {
            SKIP_WHILE(isspace);
            float f;
            const int r = sscanf(data, "%f", &f);
            ABORT_IF(r == 0 || r == EOF, "Failed to parse float");
            if (buf_size == buf_count) {
                buf_size *= 2;
                buffer = (float*)realloc(buffer, buf_size * sizeof(float));
            }
            buffer[buf_count++] = f;

            SKIP_WHILE(!isspace);
        }
    }
      
      





Vous pouvez remarquer que dans le code, il y en a un sscanf



qui lit une valeur flottante depuis le début du flux de données et vérifie à chaque fois la longueur de la chaîne entière .



Oui, j'ai fait la même erreur que les programmeurs qui travaillaient sur GTA Online : j'ai soudain écrit un parseur quadratique !



Le remplacement d'un appel sscanf



par un appel a strtof



réduit le temps de chargement de près de 10 fois : de 1,8 seconde à 199 millisecondes.



[erizo] (0.000000) main.c:10      | Startup!
[erizo] (0.178082) window.c:91    | Created window
[erizo] (0.178086) window.c:95    | Made context current
[erizo] (0.184226) window.c:103   | Initialized GLEW
[erizo] (0.194469) window.c:91    | Created window
[erizo] (0.194472) window.c:95    | Made context current
[erizo] (0.196126) loader.c:109   | Parsed ASCII STL
[erizo] (0.196866) loader.c:227   | Workers have deduplicated vertices
[erizo] (0.196871) loader.c:237   | Got 5146 vertices (7982 triangles)
[erizo] (0.196921) loader.c:240   | Waiting for buffer...
[erizo] (0.197013) loader.c:326   | Allocated buffer
[erizo] (0.197082) loader.c:253   | Sent buffers to worker threads
[erizo] (0.197303) loader.c:258   | Joined worker threads
[erizo] (0.197306) loader.c:279   | Loader thread done
[erizo] (0.199328) instance.c:32  | Showed window
      
      








C'est un parfait rappel que même si vous programmez depuis de nombreuses années, il y a toujours des pièges . En sscanf



ne remplissant pas sa complexité temporelle, il est donc très rusé de se tirer une balle dans le pied, et il me semble que personne ne m'a erré dans les ténèbres de l'ignorance.



Vous ne vous en souviendrez peut-être pas vous-même, mais chaque fois que vous lisez une histoire géniale sur le mauvais code, rappelez-vous - cela peut vous arriver aussi !



(Évidemment, la morale de l'histoire est la suivante : n'utilisez pas de sscanf



jetons uniques du début d'une ligne à l'analyse plusieurs fois ; je suis sûr que tout ira bien si vous l'évitez.)






Publicité



VDSina propose des VPS puissants et abordables avec un salaire quotidien. Le canal Internet pour chaque serveur est de 500 Mégabits, une protection contre les attaques DDoS est incluse dans le tarif, la possibilité d'installer Windows, Linux ou l'OS en général à partir de votre image, et aussi un panneau de contrôle de serveur propriétaire très pratique . Essayez-le !






All Articles