"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Golang : comment l'observabilité et le profilage ont révélé une limitation presque indétectable

Golang : comment l'observabilité et le profilage ont révélé une limitation presque indétectable

Publié le 2024-11-08
Parcourir:379

Dans un projet personnel avec Go, qui obtient des informations sur les actifs financiers auprès de Bovespa.
Le système utilise intensivement la concurrence et le parallélisme avec les goroutines, mettant à jour les informations sur les actifs (ainsi que les calculs commerciaux) toutes les 8 secondes.
Au départ, aucune erreur ni avertissement n'est apparu, mais j'ai remarqué que certaines goroutines prenaient plus de temps que d'autres à s'exécuter.

Pour être plus précis, alors que le temps p99 était de 0,03 ms, à certains moments, il a augmenté à 0,9 ms. Cela m'a amené à approfondir le problème.

J'ai découvert que j'utilisais un pool de goroutines de sémaphore, créé sur la base de la variable GOMAXPROCS.
Cependant, j'ai réalisé qu'il y avait un problème avec cette approche.

Lorsque nous utilisons la variable GOMAXPROCS, elle ne capture pas correctement le nombre de cœurs disponibles dans le conteneur. Si le conteneur a moins de cœurs disponibles que le total de la VM, il prend en compte le total de la VM. Par exemple, ma VM a 8 cœurs disponibles, mais le conteneur n'en avait que 4. Cela a entraîné la création de 8 goroutines pour s'exécuter en même temps, provoquant une limitation.

Après de nombreuses recherches du jour au lendemain, j'ai trouvé une bibliothèque développée par Uber qui ajuste automatiquement la variable GOMAXPROCS plus efficacement, qu'elle se trouve dans un conteneur ou non. Cette solution s'est avérée extrêmement stable et efficace : automaxprocs

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável uber-aller / procédures automatiques maximales

Définissez automatiquement GOMAXPROCS pour qu'il corresponde au quota de CPU du conteneur Linux.

automaxprocs Golang: Como a observabilidade e profiling revelaram um throttling quase indetectávelGolang: Como a observabilidade e profiling revelaram um throttling quase indetectávelGolang: Como a observabilidade e profiling revelaram um throttling quase indetectável

Définissez automatiquement GOMAXPROCS pour qu'il corresponde au quota de CPU du conteneur Linux.

Installation

aller chercher -u go.uber.org/automaxprocs

Démarrage rapide

import _ "go.uber.org/automaxprocs"

func main() {
  // Your application logic here.
}
Passer en mode plein écranQuitter le mode plein écran

Performance

Données mesurées à partir de l'équilibreur de charge interne d'Uber. Nous avons exécuté l'équilibreur de charge avec un quota de CPU de 200 % (c'est-à-dire 2 cœurs) :

GOMAXPROCS RPS P50 (ms) P99.9 (ms)
1 28 893,18 1,46 19.70
2 (égal au quota) 44 715,07 0,84 26,38
3 44 212,93 0,66 30.07
4 41 071,15 0,57 42,94
8 33 111,69 0,43 64,32
Par défaut (24) 22 191,40 0,45 76,19

Lorsque GOMAXPROCS est augmenté au-dessus du quota de CPU, nous constatons une légère diminution de P50, mais une augmentation significative jusqu'à P99. Nous constatons également que le total des RPS traités diminue également.

Lorsque GOMAXPROCS est supérieur au quota de processeur alloué, nous avons également constaté une limitation importante :

$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat
nr_periods 42227334
nr_throttled 131923
throttled_time 88613212216618

Une fois GOMAXPROCS réduit pour correspondre au quota du processeur, nous n'avons constaté aucune limitation du processeur.

Voir sur GitHub
.

Après avoir implémenté l'utilisation de cette bibliothèque, le problème a été résolu et le temps p99 est désormais resté constamment à 0,02 ms. Cette expérience a mis en évidence l'importance de l'observabilité et du profilage dans les systèmes concurrents.

Ce qui suit est un exemple très simple, mais qui démontre la différence de performances.

À l'aide du package de tests natifs et de benckmak de Go, j'ai créé deux fichiers :

benchmarking_with_enhancement_test.go :

package main

import (
    _ "go.uber.org/automaxprocs"
    "runtime"
    "sync"
    "testing"
)

// BenchmarkWithEnhancement Função com melhoria, para adicionar o indice do loop em um array de inteiro
func BenchmarkWithEnhancement(b *testing.B) {
    // Obtém o número de CPUs disponíveis
    numCPUs := runtime.NumCPU()
    // Define o máximo de CPUs para serem usadas pelo programa
    maxGoroutines := runtime.GOMAXPROCS(numCPUs)
    // Criação do semáforo
    semaphore := make(chan struct{}, maxGoroutines)

    var (
        // Espera para grupo de goroutines finalizar
        wg sync.WaitGroup
        // Propriade
        mu sync.Mutex
        // Lista para armazenar inteiros
        list []int
    )

    // Loop com mihão de indices
    for i := 0; i 



benchmarking_without_enhancement_test.go :

package main

import (
    "runtime"
    "sync"
    "testing"
)

// BenchmarkWithoutEnhancement Função sem a melhoria, para adicionar o indice do loop em um array de inteiro
func BenchmarkWithoutEnhancement(b *testing.B) {
    // Obtém o número de CPUs disponíveis
    numCPUs := runtime.NumCPU()
    // Define o máximo de CPUs para serem usadas pelo programa
    maxGoroutines := runtime.GOMAXPROCS(numCPUs)
    // Criação do semáforo
    semaphore := make(chan struct{}, maxGoroutines)

    var (
        // Espera para grupo de goroutines finalizar
        wg sync.WaitGroup
        // Propriade
        mu sync.Mutex
        // Lista para armazenar inteiros
        list []int
    )

    // Loop com mihão de indices
    for i := 0; i 



La différence entre eux est que l'on utilise l'importation de la bibliothèque Uber.

Lors de l'exécution du benchmark en supposant que 2 processeurs seraient utilisés, le résultat était :

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável

ns/op : fournit une moyenne en nanosecondes du temps nécessaire pour effectuer une opération spécifique.

Notez que le total disponible de mon processeur est de 8 cœurs, et c'est ce que la propriété runtime.NumCPU() a renvoyé. Cependant, comme lors de l'exécution du benchmark, j'ai défini que l'utilisation serait de seulement deux processeurs, et le fichier qui n'utilisait pas automaxprocs, j'ai défini que la limite d'exécution à la fois serait de 8 goroutines, tandis que la plus efficace serait de 2, car de cette façon, utiliser moins d'allocation rend l'exécution plus efficace.

Ainsi, l'importance de l'observabilité et du profilage de nos applications est claire.

Déclaration de sortie Cet article est reproduit sur : https://dev.to/mggcmatheus/golang-como-a-observabilidade-e-profiling-revelaram-um-throttling-quase-indetectavel-1h5p?1 En cas d'infraction, veuillez contacter study_golang @163.com supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3