"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Golang: cómo la observabilidad y la elaboración de perfiles revelaron una limitación casi indetectable

Golang: cómo la observabilidad y la elaboración de perfiles revelaron una limitación casi indetectable

Publicado el 2024-11-08
Navegar:142

En un proyecto personal con Go, que obtiene información sobre activos financieros de Bovespa.
El sistema hace un uso intensivo de la concurrencia y el paralelismo con gorutinas, actualizando la información de los activos (junto con los cálculos comerciales) cada 8 segundos.
Inicialmente, no aparecieron errores ni advertencias, pero noté que algunas rutinas tardaban más que otras en ejecutarse.

Para ser más específicos, mientras que el tiempo p99 fue de 0,03 ms, en algunos puntos aumentó a 0,9 ms. Esto me llevó a investigar más el problema.

Descubrí que estaba usando un grupo de rutinas de semáforo, que se creó en función de la variable GOMAXPROCS.
Sin embargo, me di cuenta de que había un problema con este enfoque.

Cuando usamos la variable GOMAXPROCS, no captura correctamente la cantidad de núcleos disponibles en el contenedor. Si el contenedor tiene menos núcleos disponibles que el total de la VM, considera el total de la VM. Por ejemplo, mi máquina virtual tiene 8 núcleos disponibles, pero el contenedor solo tenía 4. Esto resultó en la creación de 8 gorutinas para ejecutarse al mismo tiempo, lo que provocó una limitación.

Después de mucha investigación durante la noche, encontré una biblioteca desarrollada por Uber que ajusta automáticamente la variable GOMAXPROCS de manera más eficiente, independientemente de si está en un contenedor o no. Esta solución demostró ser extremadamente estable y eficiente: automaxprocs

Golang: Como a observabilidade e profiling revelaram um throttling quase indetectável uber-go / automaxprocs

Configure automáticamente GOMAXPROCS para que coincida con la cuota de CPU del contenedor de 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

Configura automáticamente GOMAXPROCS para que coincida con la cuota de CPU del contenedor de Linux.

Instalación

ve a buscar -u go.uber.org/automaxprocs

Inicio rápido

import _ "go.uber.org/automaxprocs"

func main() {
  // Your application logic here.
}
Ingresar al modo de pantalla completaSalir del modo de pantalla completa

Actuación

Datos medidos desde el balanceador de carga interno de Uber. Ejecutamos el equilibrador de carga con una cuota de CPU del 200 % (es decir, 2 núcleos):

GOMAXPROCS RPS P50 (ms) P99.9 (ms)
1 28.893,18 1.46 19.70
2 (igual a cuota) 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
Predeterminado (24) 22.191,40 0,45 76.19

Cuando GOMAXPROCS aumenta por encima de la cuota de CPU, vemos que P50 disminuye ligeramente, pero vemos aumentos significativos a P99. También vemos que el total de RPS manejados también disminuye.

Cuando GOMAXPROCS es mayor que la cuota de CPU asignada, también vimos una limitación significativa:

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

Una vez que se redujo GOMAXPROCS para igualar la cuota de CPU, no vimos ninguna limitación de CPU.

Ver en GitHub
.

Después de implementar el uso de esta biblioteca, el problema se resolvió y ahora el tiempo p99 se mantuvo en 0,02 ms constantemente. Esta experiencia destacó la importancia de la observabilidad y la elaboración de perfiles en sistemas concurrentes.

El siguiente es un ejemplo muy simple, pero que demuestra la diferencia en el rendimiento.

Utilizando las pruebas nativas de Go y el paquete benkmak, creé dos archivos:

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 diferencia entre ellos es que uno usa la importación de la biblioteca de Uber.

Al ejecutar el punto de referencia suponiendo que se usarían 2 CPU, el resultado fue:

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

ns/op: proporciona un promedio en nanosegundos de cuánto tiempo lleva realizar una operación específica.

Tenga en cuenta que el total disponible de mi CPU es de 8 núcleos, y eso es lo que devolvió la propiedad runtime.NumCPU(). Sin embargo, al igual que al ejecutar el benchmark, definí que el uso sería solo de dos CPU, y el archivo que no usaba automaxprocs, definí que el límite de ejecución a la vez sería de 8 gorutinas, mientras que la más eficiente sería 2, porque de esta manera, usar menos asignación hace que la ejecución sea más eficiente.

Por lo tanto, la importancia de la observabilidad y la elaboración de perfiles de nuestras aplicaciones es clara.

Declaración de liberación Este artículo se reproduce en: https://dev.to/mggcmatheus/golang-como-a-observabilidade-e-profiling-revelaram-um-throttling-quase-indetectavel-1h5p?1 Si hay alguna infracción, comuníquese con Study_golang @163.com eliminar
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3