Bovespa로부터 금융 자산에 대한 정보를 얻는 Go와의 개인 프로젝트에서.
시스템은 고루틴과의 동시성 및 병렬성을 집중적으로 사용하여 8초마다 자산 정보(비즈니스 계산과 함께)를 업데이트합니다.
처음에는 오류나 경고가 나타나지 않았지만 일부 고루틴이 다른 고루틴보다 실행하는 데 시간이 오래 걸리는 것을 발견했습니다.
좀 더 구체적으로 말하면, p99 시간은 0.03ms였지만 어떤 지점에서는 0.9ms로 증가했습니다. 이로 인해 문제를 더 자세히 조사하게 되었습니다.
GOMAXPROCS 변수를 기반으로 생성된 세마포어 고루틴 풀을 사용하고 있음을 발견했습니다.
그런데 이 접근 방식에 문제가 있다는 것을 깨달았습니다.
GOMAXPROCS 변수를 사용하면 컨테이너에서 사용 가능한 코어 수가 올바르게 캡처되지 않습니다. 컨테이너에 사용 가능한 코어 수가 VM의 전체 코어보다 적은 경우 VM의 전체 코어를 고려합니다. 예를 들어 내 VM에는 사용 가능한 코어가 8개 있지만 컨테이너에는 4개만 있었습니다. 이로 인해 동시에 실행되도록 8개의 고루틴이 생성되어 제한이 발생했습니다.
밤새 많은 조사 끝에 컨테이너에 있는지 여부에 관계없이 GOMAXPROCS 변수를 보다 효율적으로 자동 조정하는 Uber에서 개발한 라이브러리를 발견했습니다. 이 솔루션은 매우 안정적이고 효율적인 것으로 입증되었습니다. automaxprocs
Linux 컨테이너 CPU 할당량과 일치하도록 GOMAXPROCS를 자동으로 설정합니다.
가서 -u go.uber.org/automaxprocs
import _ "go.uber.org/automaxprocs"
func main() {
// Your application logic here.
}
Uber 내부 로드 밸런서에서 측정된 데이터입니다. 200% CPU 할당량(즉, 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가 CPU 할당량 이상으로 증가하면 P50이 약간 감소하지만 P99로 크게 증가하는 것을 볼 수 있습니다. 또한 처리된 총 RPS도 감소하는 것을 볼 수 있습니다.
GOMAXPROCS가 할당된 CPU 할당량보다 높을 때 상당한 제한이 발생했습니다.
$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat nr_periods 42227334 nr_throttled 131923 throttled_time 88613212216618
GOMAXPROCS가 CPU 할당량에 맞게 줄어들자 CPU 조절이 발생하지 않았습니다.
이 라이브러리 사용을 구현한 후 문제가 해결되었으며 현재 p99 시간은 0.02ms로 계속 유지됩니다. 이 경험은 동시 시스템에서 관찰 가능성과 프로파일링의 중요성을 강조했습니다.
다음은 매우 간단한 예이지만 성능의 차이를 보여주는 예입니다.
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 라이브러리 가져오기를 사용한다는 것입니다.
CPU 2개가 사용된다고 가정하고 벤치마크를 실행했을 때 결과는 다음과 같습니다.
ns/op: 특정 작업을 수행하는 데 걸리는 시간을 나노초 단위로 평균 제공합니다.
내 CPU에서 사용할 수 있는 총 코어는 8개이며, 이는 런타임.NumCPU() 속성이 반환한 것입니다. 하지만 벤치마크를 실행하면서 CPU 2개만 사용하도록 정의했고, automaxprocs를 사용하지 않은 파일은 한 번에 실행 제한을 8개 고루틴으로 정의했는데, 가장 효율적인 것은 2개로 정의했습니다. 이렇게 하면 더 적은 할당을 사용하여 실행이 더 효율적이 됩니다.
따라서 애플리케이션의 관찰 가능성과 프로파일링의 중요성은 분명합니다.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3