В личном проекте Go, который получает информацию о финансовых активах от Bovespa.
Система интенсивно использует параллелизм и параллелизм с горутинами, обновляя информацию об активах (наряду с бизнес-расчетами) каждые 8 секунд.
Первоначально никаких ошибок или предупреждений не появлялось, но я заметил, что некоторые горутины выполнялись дольше, чем другие.
Если быть более конкретным, то время p99 составляло 0,03 мс, в некоторые моменты оно увеличивалось до 0,9 мс. Это побудило меня глубже изучить проблему.
Я обнаружил, что использую пул горутин семафоров, который был создан на основе переменной GOMAXPROCS.
Однако я понял, что в этом подходе есть проблема.
Когда мы используем переменную GOMAXPROCS, она неправильно фиксирует количество ядер, доступных в контейнере. Если в контейнере доступно меньше ядер, чем общее количество виртуальной машины, учитывается общее количество виртуальных машин. Например, у моей виртуальной машины есть 8 доступных ядер, но в контейнере их было только 4. В результате было создано 8 горутин для одновременной работы, что вызвало регулирование.
После долгих исследований за ночь я нашел библиотеку, разработанную Uber, которая более эффективно автоматически настраивает переменную GOMAXPROCS, независимо от того, находится она в контейнере или нет. Это решение оказалось чрезвычайно стабильным и эффективным: automaxprocs
Автоматически устанавливать GOMAXPROCS в соответствии с квотой процессора контейнера Linux.
go get -u go.uber.org/automaxprocs
import _ "go.uber.org/automaxprocs"
func main() {
// Your application logic here.
}
Данные получены с помощью внутреннего балансировщика нагрузки Uber. Мы запустили балансировщик нагрузки с квотой ЦП 200 % (т. е. с 2 ядрами):
GOMAXPROCS | RPS | P50 (мс) | P99,9 (мс) |
---|---|---|---|
1 | 28 893,18 | 1,46 | 19,70 |
2 (равен квоте) | 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 |
По умолчанию (24) | 22 191,40 | 0,45 | 76,19 |
Когда GOMAXPROCS увеличивается сверх квоты ЦП, мы видим незначительное уменьшение P50, но значительное увеличение до P99. Мы также видим, что общее количество обработанных запросов в секунду также уменьшается.
Когда GOMAXPROCS превышает выделенную квоту ЦП, мы также наблюдаем значительное регулирование:
$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat nr_periods 42227334 nr_throttled 131923 throttled_time 88613212216618
После того как GOMAXPROCS был уменьшен в соответствии с квотой ЦП, мы не заметили регулирования ЦП.
После реализации использования этой библиотеки проблема решилась, и теперь время p99 оставалось постоянно на уровне 0,02 мс. Этот опыт подчеркнул важность наблюдаемости и профилирования в параллельных системах.
Ниже приведен очень простой пример, но демонстрирующий разницу в производительности.
Используя встроенное тестирование Go и пакет benckmak, я создал два файла:
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; ibenchmarking_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Разница между ними в том, что используется импорт библиотеки Uber.
При запуске теста, предполагая, что будут использоваться 2 процессора, результат был следующим:
ns/op: показывает среднее время в наносекундах, необходимое для выполнения определенной операции.
Обратите внимание, что общее количество доступных процессоров составляет 8 ядер, и именно это возвращает свойство runtime.NumCPU(). Однако, как и при запуске теста, я определил, что будут использоваться только два процессора, а файл, который не использовал automaxprocs, определил, что предел выполнения за раз будет 8 горутин, а наиболее эффективным будет 2, потому что таким образом, использование меньшего количества ресурсов делает выполнение более эффективным.
Итак, важность наблюдения и профилирования наших приложений очевидна.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3