Tutoriel : optimiser une application Go

Ce tutoriel vous explique comment déployer une application Go intentionnellement inefficace, configurée pour collecter des données de profil. À l'aide de l'interface de Profiler, vous allez afficher les données de profil et identifier les optimisations potentielles. Ensuite, vous allez modifier l'application, la déployer, puis évaluer l'impact de la modification.

Avant de commencer

  1. Connectez-vous à votre compte Google Cloud. Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.
  2. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  3. Pour activer l'API Cloud Profiler pour votre projet, dans le volet de navigation de Google Cloud Console, cliquez sur Profiler ou sur le bouton suivant :

    Accéder à Profiler

  4. Pour ouvrir Cloud Shell, cliquez sur Activer Cloud Shell dans la barre d'outils de Google Cloud Console :

    Activer Cloud Shell

    Après quelques instants, une session Cloud Shell s'ouvre dans Google Cloud Console :

    Session Cloud Shell

Exemple d'application

L'objectif principal est de maximiser le nombre de requêtes par seconde que le serveur peut traiter. Un objectif secondaire consistera à réduire l'utilisation de la mémoire en éliminant les allocations de mémoire inutiles.

Le serveur, qui utilise un framework gRPC, reçoit un mot ou une expression, puis renvoie le nombre de fois où l'élément apparaît dans les œuvres de Shakespeare.

Des tests de charge réalisés sur le serveur déterminent le nombre moyen de requêtes par seconde qu'il est en mesure de gérer. Pour chaque série de tests, un simulateur de client est appelé et invité à émettre 20 requêtes de manière séquentielle. À la fin d'une série, le nombre de requêtes envoyées par le simulateur de client, le temps écoulé et le nombre moyen de requêtes par seconde sont affichés.

Le code du serveur est intentionnellement inefficace.

Exécuter l'exemple d'application

Téléchargez et exécutez l'exemple d'application :

  1. Dans Cloud Shell, exécutez les commandes suivantes :

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    cd golang-samples/profiler/shakesapp
    
  2. Exécutez l'application en définissant la version sur 1 et le nombre de séries sur 15 :

    go run . -version 1 -num_rounds 15
    

    Au bout d'une ou deux minutes, les données de profil s'affichent. Ces données se présentent comme suit :

    Graphique de type "flamme" initial représentant le temps CPU utilisé

    Dans la capture d'écran, notez que le type de profil est défini sur CPU time (temps CPU). Cela indique que les données d'utilisation du processeur sont affichées dans le graphique de type "flamme".

    Vous trouverez ci-dessous un exemple de résultat imprimé dans Cloud Shell :

    $ go run . -version 1 -num_rounds 15
    2020/08/27 17:27:34 Simulating client requests, round 1
    2020/08/27 17:27:34 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 17:27:34 profiler has started
    2020/08/27 17:27:34 creating a new profile via profiler service
    2020/08/27 17:27:51 Simulated 20 requests in 17.3s, rate of 1.156069 reqs / sec
    2020/08/27 17:27:51 Simulating client requests, round 2
    2020/08/27 17:28:10 Simulated 20 requests in 19.02s, rate of 1.051525 reqs / sec
    2020/08/27 17:28:10 Simulating client requests, round 3
    2020/08/27 17:28:29 Simulated 20 requests in 18.71s, rate of 1.068947 reqs / sec
    ...
    2020/08/27 17:44:32 Simulating client requests, round 14
    2020/08/27 17:46:04 Simulated 20 requests in 1m32.23s, rate of 0.216849 reqs / sec
    2020/08/27 17:46:04 Simulating client requests, round 15
    2020/08/27 17:47:52 Simulated 20 requests in 1m48.03s, rate of 0.185134 reqs / sec
    

    Dans Cloud Shell, le résultat affiche le temps écoulé pour chaque itération, ainsi que le taux de requêtes moyen. Une fois l'application démarrée, l'entrée "Simulated 20 requests in 17.3s, rate of 1.156069 reqs / sec" (20 requêtes simulées en 17,3 s, taux de 1,156069 requêtes/s) indique que le serveur exécute environ une requête par seconde. Dans la dernière série, l'entrée "Simulated 20 requests in 1m48.03s, rate of 0.185134 reqs / sec" (20 requêtes simulées en 1 min 48,03 s, taux de 0,185134 requête/s) indique que le serveur exécute environ une requête toutes les cinq secondes.

Utiliser des profils de temps CPU pour maximiser le nombre de requêtes par seconde

Une approche pour maximiser le nombre de requêtes par seconde consiste à identifier les méthodes occasionnant une utilisation intensive du processeur et à optimiser leurs mises en œuvre. Dans cette section, vous allez employer des profils de temps CPU pour identifier une méthode occasionnant une utilisation intensive du processeur dans le serveur.

Déterminer le temps CPU utilisé

Le cadre racine du graphique de type "flamme" indique le temps CPU total utilisé par l'application sur l'intervalle de collecte de 10 secondes :

Vue développée du cadre racine du graphique de type "flamme"

Dans cet exemple, le service a utilisé 2.37 s. Lorsque le système s'exécute sur un seul cœur, une utilisation du temps CPU de 2,37 secondes équivaut à 23,7 % d'utilisation du cœur. Pour en savoir plus, consultez la section Types de profilage disponibles.

Modifier l'application

Évaluer la modification

Pour évaluer la modification, procédez comme suit :

  1. Exécutez l'application en définissant sa version sur 2 :

    go run . -version 2 -num_rounds 40
    

    Une section ultérieure montre qu'avec l'optimisation, le temps nécessaire à l'exécution d'une série est nettement inférieur à celui de l'application non modifiée. Pour garantir que l'application s'exécute suffisamment longtemps pour collecter et importer des profils, le nombre de séries est plus élevé.

  2. Attendez que l'application termine de s'exécuter, puis affichez les données de profil de cette version :

    • Cliquez sur MAINTENANT pour charger les données de profil les plus récentes. Pour en savoir plus, consultez la section Période d'affichage.
    • Dans le menu Version, sélectionnez 2.

À titre d'exemple, le graphique de type "flamme" se présente comme suit :

Graphique de type "flamme" montrant le temps CPU utilisé par la version 2

Dans cette figure, le cadre racine affiche la valeur 7.8 s. Suite à la modification de la fonction de correspondance de chaîne, le temps CPU utilisé par l'application est passé de 2,37 secondes à 7,8 secondes. En d'autres termes, l'application qui utilisait auparavant 23,7 % d'un cœur de processeur en utilise désormais 78 %.

La largeur du cadre est une mesure proportionnelle du temps CPU utilisé. Dans cet exemple, la largeur du cadre pour GetMatchCount indique que la fonction utilise environ 49 % de l'ensemble du temps CPU utilisé par l'application. Dans le graphique de type "flamme" initial, ce même cadre représentait environ 72 % de la largeur du graphique. Pour connaître le temps CPU utilisé exact, vous pouvez afficher l'info-bulle du cadre ou exploiter la liste des fonctions de focus :

Liste des fonctions de focus montrant le temps CPU utilisé par la version 2.

Le résultat dans Cloud Shell indique que la version modifiée effectue environ 5,8 requêtes par seconde :

$ go run . -version 2 -num_rounds 40
2020/08/27 18:21:40 Simulating client requests, round 1
2020/08/27 18:21:40 Stackdriver Profiler Go Agent version: 20200618
2020/08/27 18:21:40 profiler has started
2020/08/27 18:21:40 creating a new profile via profiler service
2020/08/27 18:21:44 Simulated 20 requests in 3.67s, rate of 5.449591 reqs / sec
2020/08/27 18:21:44 Simulating client requests, round 2
2020/08/27 18:21:47 Simulated 20 requests in 3.72s, rate of 5.376344 reqs / sec
2020/08/27 18:21:47 Simulating client requests, round 3
2020/08/27 18:21:51 Simulated 20 requests in 3.58s, rate of 5.586592 reqs / sec
...
2020/08/27 18:23:51 Simulating client requests, round 39
2020/08/27 18:23:54 Simulated 20 requests in 3.46s, rate of 5.780347 reqs / sec
2020/08/27 18:23:54 Simulating client requests, round 40
2020/08/27 18:23:58 Simulated 20 requests in 3.4s, rate of 5.882353 reqs / sec

La modification mineure de l'application a eu deux effets différents :

  • Le nombre de requêtes est passé de moins d'une requête par seconde à 5,8 requêtes par seconde.

  • Le temps CPU par requête, calculé en divisant l'utilisation du processeur par le nombre de requêtes par seconde, est passé de 23,7 % à 13,4 %.

    Notez que le temps CPU par requête a diminué même si le temps CPU utilisé est passé de 2,37 secondes (ce qui correspond à une utilisation de 23,7 % d'un cœur de processeur) à 7,8 secondes (soit 78 % d'utilisation d'un cœur de processeur).

Utiliser des profils de segments de mémoire alloués pour améliorer l'utilisation des ressources

Cette section explique comment utiliser les profils de segments de mémoire et les profils de segments de mémoire alloués pour identifier une méthode occasionnant une allocation intensive dans l'application :

  • Les profils de segments de mémoire indiquent la quantité de mémoire allouée dans le segment de mémoire du programme au moment de la collecte du profil.

  • Les profils de segments de mémoire alloués indiquent la quantité totale de mémoire allouée dans le segment de mémoire du programme durant l'intervalle où la collecte du profil a été effectuée. En divisant ces valeurs par 10 secondes (l'intervalle de collecte des profils), vous pouvez les interpréter en tant que taux d'allocation.

Activer la collecte des profils de segments de mémoire

  1. Exécutez l'application en définissant sa version sur 3 et activez la collecte de profils de segments de mémoire et de profils de segments de mémoire alloués.

    go run . -version 3 -num_rounds 40 -heap -heap_alloc
    
  2. Attendez que l'application termine de s'exécuter, puis affichez les données de profil de cette version :

    • Cliquez sur MAINTENANT pour charger les données de profil les plus récentes.
    • Dans le menu Version, sélectionnez 3.
    • Dans le menu Type de profileur, sélectionnez Segment de mémoire alloué.

    À titre d'exemple, le graphique de type "flamme" se présente comme suit :

    Graphique de type "flamme" des profils de segments de mémoire alloués pour la version 3

Identifier le taux d'allocation de segments de mémoire

Le cadre racine affiche la quantité totale de segments de mémoire alloués pendant les 10 secondes où un profil a été collecté, calculée en moyenne sur tous les profils. Dans cet exemple, le cadre racine indique que, en moyenne, 1,535 Gio de mémoire a été alloué.

Modifier l'application

Évaluer la modification

Pour évaluer la modification, procédez comme suit :

  1. Exécutez l'application en définissant sa version sur 4 :

    go run . -version 4 -num_rounds 60 -heap -heap_alloc
    
  2. Attendez que l'application termine de s'exécuter, puis affichez les données de profil de cette version :

    • Cliquez sur MAINTENANT pour charger les données de profil les plus récentes.
    • Dans le menu Version, sélectionnez 4.
    • Dans le menu Type de profileur, sélectionnez Segment de mémoire alloué.
  3. Pour quantifier l'impact de la modification de readFiles sur le taux d'allocation de segments de mémoire, comparez les profils de segments de mémoire alloués pour la version 4 à ceux collectés pour la version 3 :

    Comparaison des profils de segments de mémoire alloués entre les versions 4 et 3

    L'info-bulle du cadre racine indique que, dans la version 4, la quantité moyenne de mémoire allouée lors de la collecte des profils a diminué de 1,301 Gio par rapport à la version 3. L'info-bulle de readFiles.func1 indique une diminution de 1,045 Gio :

    Comparaison des info-bulles de readfiles pour le type de profil "Segment de mémoire alloué"

  4. Pour quantifier l'impact sur la récupération de mémoire, configurez une comparaison des profils de temps CPU. Dans la capture d'écran ci-dessous, un filtre est appliqué afin d'afficher les piles du récupérateur de mémoire Go runtime.gcBgMarkWorker.*. La capture d'écran montre que l'utilisation du processeur pour la récupération de mémoire est passée de 16,8 % à 4,97 %.

    Comparaison du temps CPU utilisé par un processus de récupération de mémoire en arrière-plan entre les versions 4 et 3

  5. Pour déterminer si la modification a eu un impact sur le nombre de requêtes par seconde traitées par l'application, affichez le résultat dans Cloud Shell. Dans cet exemple, la version 4 exécute jusqu'à 15 requêtes par seconde, ce qui est nettement supérieur aux 5,8 requêtes par seconde de la version 3 :

    $ go run . -version 4 -num_rounds 60 -heap -heap_alloc
    2020/08/27 21:51:42 Simulating client requests, round 1
    2020/08/27 21:51:42 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 21:51:42 profiler has started
    2020/08/27 21:51:42 creating a new profile via profiler service
    2020/08/27 21:51:44 Simulated 20 requests in 1.47s, rate of 13.605442 reqs / sec
    2020/08/27 21:51:44 Simulating client requests, round 2
    2020/08/27 21:51:45 Simulated 20 requests in 1.3s, rate of 15.384615 reqs / sec
    2020/08/27 21:51:45 Simulating client requests, round 3
    2020/08/27 21:51:46 Simulated 20 requests in 1.31s, rate of 15.267176 reqs / sec
    ...
    

    L'augmentation du nombre de requêtes par seconde traitées par l'application peut s'expliquer par la réduction du temps consacré à la récupération de mémoire.

  • Vous pouvez consulter les profils de segments de mémoire pour mieux comprendre l'impact de la modification de readFiles. En comparant les profils de segments de mémoire entre les versions 3 et 4, on peut constater que l'utilisation des segments de mémoire est passée de 70,95 Mio à 18,47 Mio :

    Comparaison de l'utilisation des segments de mémoire entre les versions 4 et 3.

Résumé

Dans ce guide de démarrage rapide, vous avez pu identifier les optimisations potentielles à apporter à une application en vous basant sur le temps CPU et sur des profils de segments de mémoire alloués. Les objectifs consistaient à optimiser le nombre de requêtes par seconde et à éliminer les allocations inutiles.

  • À l'aide de profils de temps CPU, vous avez pu identifier une fonction occasionnant une utilisation intensive du processeur. Après l'application d'une modification simple, le taux de requêtes du serveur est passé d'environ 1 par seconde à 5,8 par seconde.

  • À l'aide de profils de segments de mémoire alloués, vous avez pu découvrir que la fonction shakesapp/server.go readFiles avait un taux d'allocation élevé. Après l'optimisation de readFiles, le taux de requêtes du serveur est passé à 15 requêtes par seconde et la quantité moyenne de mémoire allouée lors des 10 secondes de collecte des profils a diminué de 1,301 Gio.

Étape suivante

Pour en savoir plus sur l'exécution de l'agent Cloud Profiler, consultez les pages suivantes :