Aller au contenu

Guide d'Analyse de la Compilation & de l'Assembleur (Juillet 2026)

Date : 3 juillet 2026 Statut : Guide de Référence

Ce guide fournit des instructions concrètes et étape par étape pour utiliser Compiler Explorer (Godbolt.org) et NVIDIA Nsight Graphics (Shader Profiler) afin d'auditer et d'optimiser l'exécution CPU et GPU dans la base de code suckless-ogl.


1. Compiler Explorer (Godbolt.org)

Compiler Explorer est un outil interactif permettant d'analyser le code assembleur généré par les compilateurs et de valider les optimisations (telles que l'auto-vectorisation AVX2, le padding des structures et le nombre d'instructions).

Scénario A : Vérification de la Vectorisation AVX2/FMA (N-Body Physics)

Pour vérifier si la boucle critique des calculs de gravité N-Body est correctement vectorisée sans surcharge d'assemblage :

  1. Ouvrir Godbolt : Allez sur godbolt.org.
  2. Configurer le Langage : Définissez le menu déroulant du langage source à gauche sur C.
  3. Configurer le Compilateur : Définissez le compilateur à droite sur x86-64 gcc 14.2 (ou supérieur).
  4. Coller le Code : Copiez et collez le snippet autonome de simulation N-Body suivant dans l'éditeur :
    #include <math.h>
    
    typedef double dvec3[3];
    
    typedef struct {
        dvec3 position;
        dvec3 velocity;
        double mass;
        double radius;
    } Body;
    
    typedef struct {
        Body bodies[128];
        int body_count;
        float gravity;
    } NBodySim;
    
    static inline void dvec3_zero(dvec3 dest) {
        dest[0] = 0.0; dest[1] = 0.0; dest[2] = 0.0;
    }
    static inline void dvec3_sub(const dvec3 a, const dvec3 b, dvec3 dest) {
        dest[0] = a[0] - b[0]; dest[1] = a[1] - b[1]; dest[2] = a[2] - b[2];
    }
    static inline double dvec3_dot(const dvec3 a, const dvec3 b) {
        return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
    }
    static inline double dvec3_norm2(const dvec3 v) {
        return dvec3_dot(v, v);
    }
    static inline void dvec3_scale(const dvec3 v, double scalar, dvec3 dest) {
        dest[0] = v[0] * scalar; dest[1] = v[1] * scalar; dest[2] = v[2] * scalar;
    }
    static inline void dvec3_addto(dvec3 dest, const dvec3 b) {
        dest[0] += b[0]; dest[1] += b[1]; dest[2] += b[2];
    }
    static inline void dvec3_subfrom(dvec3 dest, const dvec3 b) {
        dest[0] -= b[0]; dest[1] -= b[1]; dest[2] -= b[2];
    }
    
    void compute_accelerations(const NBodySim* sim, double accel[][3]) {
        for (int i = 0; i < sim->body_count; i++) {
            dvec3_zero(accel[i]);
        }
    
        for (int i = 0; i < sim->body_count; i++) {
            for (int j = i + 1; j < sim->body_count; j++) {
                dvec3 diff;
                dvec3_sub(sim->bodies[j].position, sim->bodies[i].position, diff);
    
                double eps2 = 0.001; // pair softening
                double dist_sq = dvec3_norm2(diff) + eps2;
                double inv_dist = 1.0 / sqrt(dist_sq);
                double inv_dist3 = inv_dist * inv_dist * inv_dist;
                double grav = (double)sim->gravity * inv_dist3;
    
                dvec3 force;
                dvec3_scale(diff, grav * sim->bodies[j].mass, force);
                dvec3_addto(accel[i], force);
    
                dvec3_scale(diff, grav * sim->bodies[i].mass, force);
                dvec3_subfrom(accel[j], force);
            }
        }
    }
    
  5. Définir les Options de Build :
  6. Saisissez -O3 -march=haswell -ffast-math dans la zone de texte Compiler Options.
  7. Analyser l'Assembleur :
  8. Regardez le panneau d'assembleur généré. Vous devriez voir des instructions comme vfmadd213pd (Fused Multiply-Add) et des opérations packées vmulpd, vaddpd utilisant des registres 256 bits %ymm.
  9. Essayez de changer les options pour -O1 ou de supprimer -ffast-math pour voir comment le compilateur revient à des instructions mathématiques scalaires (mulsd, addsd).

Scénario B : Vérification de l'Alignement des Structures (UBO / SSBO)

Lors de la copie de structures de C vers des Uniform Buffer Objects (UBO) ou des Shader Storage Buffer Objects (SSBO) OpenGL, la disposition mémoire doit correspondre aux normes d'alignement GLSL (std140/std430).

  1. Coller le Code :
    #include <stddef.h>
    
    typedef struct {
        float exposure;
        float gamma;
        int   contrast;
    } PostProcessUniforms;
    
    unsigned long get_struct_size() {
        return sizeof(PostProcessUniforms);
    }
    
    _Static_assert(sizeof(PostProcessUniforms) == 12, "Incorrect alignment!");
    
  2. Observer le Comportement du Compilateur :
  3. Sélectionnez x86-64 gcc 14.2 (Linux) et x64 msvc v19.latest (Windows).
  4. Vérifiez si les deux compilateurs passent l'assertion statique ou introduisent des octets de padding à la fin de la structure.
  5. Modifiez les membres de la structure (par exemple, en ajoutant des équivalents double ou vec4) et inspectez l'assembleur pour voir la taille retournée dans get_struct_size.

Scénario C : Validation des Shaders en Mode Hors-Ligne (GLSL/SPIR-V)

Pour vérifier si la syntaxe GLSL est correcte et inspecter l'assembleur SPIR-V :

  1. Configurer le Langage : Définissez le menu déroulant du langage source à gauche sur GLSL.
  2. Configurer le Compilateur : Sélectionnez glslangValidator (ou dxc).
  3. Coller le Compute Shader GLSL :
    #version 450
    layout(local_size_x = 256) in;
    
    layout(std430, binding = 0) buffer ParticleBuffer {
        vec4 positions[];
    };
    
    layout(std430, binding = 1) buffer VelocityBuffer {
        vec4 velocities[];
    };
    
    uniform float deltaTime;
    
    void main() {
        uint idx = gl_GlobalInvocationID.x;
        positions[idx] += velocities[idx] * deltaTime;
    }
    
  4. Inspecter SPIR-V : Examinez les instructions assembleurs SPIR-V générées (telles que OpAccessChain, OpFMul, OpFAdd) pour contrôler l'organisation des variables et de la logique de calcul.

2. NVIDIA Nsight Graphics (Shader Profiler)

NVIDIA Nsight Graphics est un outil de profilage de GPU haut de gamme. Alors que Godbolt valide la compilation, Nsight Graphics mesure les goulots d'étranglement matériels réels sur le GPU.

Comment Profiler des Shaders GLSL

  1. Préparer le Binaire Release :
  2. Compilez le projet en utilisant :
    cmake -B build-release -DCMAKE_BUILD_TYPE=Release
    cmake --build build-release --parallel
    
  3. Configurer Nsight Graphics :
  4. Ouvrez NVIDIA Nsight Graphics.
  5. Définissez Application Executable sur le chemin de votre binaire compilé (build-release/app).
  6. Définissez Working Directory sur la racine du projet.
  7. Sous Activity, sélectionnez GPU Trace (pour les goulots d'étranglement matériels globaux) ou Frame Debugger (pour le débogage du rendu des frames).
  8. Cliquez sur Launch.
  9. Capturer & Analyser :
  10. Exécutez la simulation et cliquez sur Generate GPU Trace (ou appuyez sur F11).
  11. Une fois le tracé terminé, accédez à l'onglet Shader Pipelines.
  12. Localisez le pipeline représentant votre compute shader (par exemple nbody.comp).
  13. Localiser les Hot Spots & Warp Stalls :
  14. Double-cliquez sur le compute shader pour ouvrir la vue Shader Profiler.
  15. Allez dans l'onglet Hot Spots. Nsight Graphics superpose le code GLSL avec des compteurs d'exécution correspondants.
  16. Inspectez la colonne Top Stall pour voir pourquoi le shader attend :
    • TEXTHR (Texture Throashing/Fetch) : Le shader est bloqué en attente d'opérations mémoire ou de lectures de cache de texture.
    • NOTSEL (Not Selected) : Les pipelines d'exécution sont occupés, en attente de ressources de planification du GPU.
    • IMCMIS (Instruction Cache Miss) : Le cache du code du shader a subi un échec, indiquant des structures de branchement complexes.
    • MATHATH (Math Latency) : Les pipelines ALU sont saturés par des calculs mathématiques complexes (FMA, transcendantes).

3. Alternatives Locales en Ligne de Commande

Génération Assembleur via Just

Pour inspecter rapidement l'assembleur localement sans envoyer de code sur Godbolt, lancez :

just asm src/app.c
Cela génère src/app.c.s localement en mode Release avec les drapeaux d'architecture native mais la LTO désactivée.

Entrelacement du Code Source (objdump)

Pour afficher votre code C côte à côte avec l'assembleur généré dans votre terminal local :

cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build
objdump -S --demangle build/app | less
Cette commande affiche le code source C d'origine directement au-dessus des instructions assembleurs correspondantes.

Débogage Temps-Réel Source & Assembleur via GDB TUI

Pour déboguer, exécuter au pas-à-pas et observer côte à côte le code source C et l'assembleur désassemblé en temps réel directement dans votre terminal : 1. Compilez le projet en mode Debug :

cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
2. Lancez GDB directement en mode TUI avec la disposition découpée (split layout) :
gdb -ex "layout split" build/app
3. Contrôlez l'exécution : * Définir un point d'arrêt : break app_run * Lancer l'application : run * Faites défiler le code source avec les touches fléchées du clavier. Pour basculer le focus vers la console GDB (pour retrouver l'historique des commandes avec les flèches), appuyez sur Ctrl+X puis O. * Avancez instruction par instruction tout en observant la synchronisation visuelle : * Avancer d'une ligne de code C : next (ou n) * Entrer dans une ligne de code C : step (ou s) * Avancer d'une instruction assembleur : nexti (ou ni) * Entrer dans une instruction assembleur : stepi (ou si)