Go 앱 최적화
이 가이드에서는 프로필 데이터를 수집하도록 구성된 의도적으로 비효율적인 Go 애플리케이션을 배포합니다. Profiler 인터페이스를 사용하여 프로필 데이터를 보고 잠재적 최적화를 파악합니다. 그런 다음 애플리케이션을 수정 및 배포하고 수정 효과를 평가합니다.
시작하기 전에
- Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Enable the required API.
- Cloud Shell을 열려면 Google Cloud Console 툴바에서 Cloud Shell 활성화를 클릭합니다.
잠시 후 Google Cloud Console에서 Cloud Shell 세션이 열립니다.
샘플 애플리케이션
기본 목표는 서버가 처리할 수 있는 초당 쿼리 수를 최대화하는 것입니다. 부수적인 목표는 불필요한 메모리 할당을 제거하여 메모리 사용량을 줄이는 것입니다.
서버는 gRPC 프레임워크를 사용하여 단어 또는 구문을 수신한 후 셰익스피어 작품에서 해당 단어 또는 구문이 나타나는 횟수를 반환합니다.
서버가 처리할 수 있는 초당 평균 쿼리 수는 서버 부하 테스트를 통해 결정됩니다. 테스트를 실행할 때마다 클라이언트 시뮬레이터가 호출되어 20개의 순차적 쿼리를 실행하도록 지시합니다. 테스트가 한 번 완료되면 클라이언트 시뮬레이터에서 전송한 쿼리 수, 경과 시간, 초당 평균 쿼리 수가 표시됩니다.
서버 코드는 의도적으로 비효율적입니다.
샘플 애플리케이션 실행
샘플 애플리케이션을 다운로드하고 실행합니다.
Cloud Shell에서 다음 명령어를 실행합니다.
git clone https://github.com/GoogleCloudPlatform/golang-samples.git cd golang-samples/profiler/shakesapp
버전을
1
로 설정하고 테스트 횟수를 15회로 설정하여 애플리케이션을 실행합니다.go run . -version 1 -num_rounds 15
1~2분 후에 프로필 데이터가 표시됩니다. 다음 예시와 유사한 프로필 데이터가 표시됩니다.
스크린샷에서 프로필 유형이
CPU time
으로 설정되어 있음을 확인합니다. 이는 CPU 사용 데이터가 Flame 그래프에 표시됨을 나타냅니다.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
Cloud Shell 출력에는 각 반복의 경과 시간과 평균 요청 비율이 표시됩니다. 애플리케이션이 시작될 때 '17.3초에 요청 20개 시뮬레이션, 초당 요청 1.156069개'라는 항목은 서버가 초당 약 1개의 요청을 실행함을 나타냅니다. 마지막 회에서 '1분 48.03초에 요청 20개 시뮬레이션, 초당 요청 0.185134개'라는 항목은 서버가 5초마다 약 1개의 요청을 실행함을 나타냅니다.
CPU 시간 프로필을 사용하여 초당 쿼리 수 최대화
초당 쿼리 수를 최대화하는 한 가지 방법은 CPU가 많이 사용되는 메서드를 파악하고 구현을 최적화하는 것입니다. 이 섹션에서는 CPU 시간 프로필을 사용하여 서버에서 CPU를 많이 사용하는 메서드를 식별합니다.
CPU 시간 사용량 확인
Flame 그래프의 루트 프레임은 애플리케이션이 10초의 수집 간격 동안 사용한 총 CPU 시간을 나열합니다.
이 예시에서 서비스는 2.37 s
를 사용했습니다. 시스템이 단일 코어에서 실행되는 경우 CPU 시간 2.37초는 코어의 23.7% 사용에 해당합니다. 자세한 내용은 사용 가능한 프로파일링 유형을 참조하세요.
애플리케이션 수정
1단계: CPU 시간을 많이 사용하는 함수
최적화가 필요할 수도 있는 코드를 확인하는 한 가지 방법은 함수 테이블을 보고 탐욕 함수를 파악하는 것입니다.
- 테이블을 보려면 list 포커스 함수 목록을 클릭합니다.
- 합계별로 테이블을 정렬합니다. 합계가 표시된 열에 함수와 그 하위 요소의 CPU 시간 사용량이 표시됩니다.
이 예시에서
GetMatchCount
는 나열되는 첫 번째shakesapp/server.go
함수입니다. 이 함수는 총 CPU 시간 중 1.7초, 즉 애플리케이션 총 CPU 시간의 72%를 사용했습니다. 이 함수는 gRPC 요청을 처리하는 것으로 알려져 있습니다.Flame 그래프는
shakesapp/server.go
함수GetMatchCount
이MatchString
를 호출하며, 후자는Compile
을 호출하는 데 대부분의 시간을 소비한다는 것을 보여줍니다.:
2단계: 배운 내용 활용 방법
- 언어에 대한 자신의 전문성을 활용합니다.
MatchString
은 정규 표현식 메서드입니다. 정규 표현식 처리는 매우 유연하지만 모든 문제에 가장 효율적인 솔루션은 아닙니다. - 애플리케이션에 대한 자신의 전문성을 활용합니다. 클라이언트가 단어 또는 구문을 생성하고 서버가 이 구문을 검색합니다.
MatchString
메서드 사용을 위해shakesapp/server.go
메서드GetMatchCount
의 실행을 검색한 다음 더 간단하고 효율적인 함수로 해당 호출을 대체할 수 있는지 확인합니다.
3단계: 애플리케이션 변경 방법
shakesapp/server.go
파일의 기존 코드에는 MatchString
에 대한 호출 1개가 포함되어 있습니다.
isMatch, err := regexp.MatchString(query, line) if err != nil { return resp, err } if isMatch { resp.MatchCount++ }
한 가지 옵션은 MatchString
논리를 strings.Contains
를 사용하는 동일 논리로 대체하는 것입니다.
if strings.Contains(line, query) { resp.MatchCount++ }
regexp
패키지의 가져오기 문을 삭제해야 합니다.
변경사항 평가
변경사항을 평가하려면 다음을 수행합니다.
애플리케이션 버전을
2
로 설정하여 애플리케이션을 실행합니다.go run . -version 2 -num_rounds 40
이후 섹션에서는 최적화를 통해 한 번 실행하는 데 걸리는 시간이 수정되지 않은 애플리케이션의 실행 시간보다 훨씬 짧게 나타납니다. 애플리케이션 실행 시간을 프로필을 수집하고 업로드할 수 있을 만큼 길게 늘리기 위해 횟수가 증가합니다.
애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.
- 지금을 클릭하여 최신 프로필 데이터를 로드합니다. 자세한 내용은 시간 범위를 참조하세요.
- 버전 메뉴에서 2를 선택합니다.
예를 들어 Flame 그래프는 다음과 같습니다.
이 그림에서 루트 프레임은 7.8 s
값을 표시합니다. 문자열 일치 함수를 변경한 결과, 애플리케이션에서 사용하는 CPU 시간이 2.37초에서 7.8초로 증가하거나 애플리케이션의 CPU 코어 사용량이 23.7%에서 78%로 변경되었습니다.
프레임 너비는 CPU 시간 사용량에 비례합니다. 이 예시에서 GetMatchCount
의 프레임 너비는 함수가 애플리케이션에서 사용한 모든 CPU 시간의 약 49%를 사용한다는 것을 나타냅니다.
원래 Flame 그래프에서 이 동일한 프레임은 그래프 너비의 약 72%를 차지했습니다.
정확한 CPU 시간 사용량을 보려면 프레임 도움말을 사용하거나 포커스 함수 목록을 사용하면 됩니다.
Cloud Shell의 출력은 수정된 버전이 초당 약 5.8개 요청을 완료함을 나타냅니다.
$ 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
애플리케이션 이 작은 변경사항에는 두 가지 효과가 있었습니다.
초당 요청 수가 초당 1개에서 5.8개로 증가했습니다.
CPU 사용률을 초당 요청 수로 나눈 요청당 CPU 시간은 23.7%에서 13.4%로 감소했습니다.
CPU 시간 사용량이 2.37초(단일 CPU 코어 사용량 23.7%)로 증가해도 요청당 CPU 시간은 7.8초(CPU 코어의 78%)로 감소합니다.
할당된 힙 프로필을 사용하여 리소스 사용량 개선
이 섹션에서는 힙과 할당된 힙 프로필을 사용하여 애플리케이션에서 많은 할당량을 사용하는 메서드를 파악하는 방법을 설명합니다.
힙 프로필은 프로필이 수집되는 순간 프로그램의 힙에 할당된 메모리 양을 보여줍니다.
할당된 힙 프로필은 프로필이 수집된 간격에 프로그램의 힙에 할당된 총 메모리 양을 표시합니다. 이러한 값을 프로필 수집 간격인 10초로 나누어 할당 속도로 변환하면 됩니다.
힙 프로필 수집 사용 설정
애플리케이션 버전을
3
로 설정된 애플리케이션을 실행하고 힙 및 할당된 힙 프로필 모음을 사용 설정합니다.go run . -version 3 -num_rounds 40 -heap -heap_alloc
애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.
- 지금을 클릭하여 최신 프로필 데이터를 로드합니다.
- 버전 메뉴에서 3을 선택합니다.
- 프로파일러 유형 메뉴에서 할당된 힙을 선택합니다.
예를 들어 Flame 그래프는 다음과 같습니다.
힙 할당 속도 확인
루트 프레임은 프로필이 수집되는 10초 동안 할당된 총 힙의 양을 모든 프로필에 대한 평균으로 표시합니다. 이 예시에서 루트 프레임은 평균적으로 1.535GiB의 메모리가 할당되었음을 보여줍니다.
애플리케이션 수정
1단계: 힙 할당 속도 최소화의 필요성
Go 백그라운드 가비지 컬렉션 함수인 runtime.gcBgMarkWorker.*
의 CPU 시간 사용량을 사용하여 가비지 컬렉션 비용을 줄이기 위해 애플리케이션을 최적화하는 것이 적절한지 확인할 수 있습니다.
- CPU 시간 사용량이 5% 미만인 경우 최적화를 건너뜁니다.
- CPU 시간 사용량이 25% 이상이면 최적화합니다.
이 예시에서 백그라운드 가비지 수집기의 CPU 시간 사용량은 16.8%입니다. 이 값은 shakesapp/server.go
최적화를 시도하기에 충분합니다.

2단계: 메모리를 많이 할당하는 함수
shakesapp/server.go
파일에는 최적화 대상인 GetMatchCount
및 readFiles
의 두 가지 함수가 있습니다. 이러한 함수의 메모리 할당 속도를 확인하려면 프로필 유형을 할당된 힙으로 설정한 다음 list포커스 함수 목록를 사용합니다.
이 예시에서 프로필을 수집하는 10초 동안 readFiles.func1
의 총 힙 할당량은 평균적으로 1.045GiB, 즉 할당된 총 메모리의 68%입니다. 프로필을 수집하는 10초 동안 자체 힙 할당은 255.4MiB입니다.

이 예시에서 Go 메서드 makeSlice
는 평균적으로 10초 프로필 수집 중에 798.7MiB를 할당합니다.
할당량을 줄이는 가장 간단한 방법은 makeSlice
호출을 줄이는 것입니다. readFiles
함수는 라이브러리 메서드를 통해 makeSlice
를 호출합니다.
이 분석 결과에서는 readFiles
를 최적화하여 힙 할당 속도를 줄일 수 있다는 점을 알 수 있습니다.
3단계: 애플리케이션 변경 방법
한 가지 방법은 파일을 한 번 읽은 후 해당 콘텐츠를 재사용하도록 애플리케이션을 수정하는 것입니다. 예를 들어 다음과 같이 변경할 수 있습니다.
- 전역 변수
files
가 초기 파일 읽기 결과를 저장하도록 정의합니다.var files []string
files
가 정의되면 일찍 반환되도록readFiles
를 수정합니다.func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) { // return if defined if files != nil { return files, nil } // Existing type resp struct { s string err error } ... // Save the result in the variable files files = make([]string, len(paths)) for i := 0; i < len(paths); i++ { r := <-resps if r.err != nil { return nil, r.err } files[i] = r.s } return files, nil }
변경사항 평가
변경사항을 평가하려면 다음을 수행합니다.
애플리케이션 버전을
4
로 설정하여 애플리케이션을 실행합니다.go run . -version 4 -num_rounds 60 -heap -heap_alloc
애플리케이션이 완료될 때까지 기다린 후 이 애플리케이션 버전의 프로필 데이터를 봅니다.
- 지금을 클릭하여 최신 프로필 데이터를 로드합니다.
- 버전 메뉴에서 4를 선택합니다.
- 프로파일러 유형 메뉴에서 할당된 힙을 선택합니다.
힙 할당 속도에서
readFiles
를 변경했을 때의 효과를 수량화하기 위해 버전 4에 할당된 힙 프로필을 3에 대해 수집된 힘 프로필과 비교합니다.루트 프레임의 도움말에서는 버전 4를 사용하면 프로필 수집 중에 할당된 메모리의 평균 양이 버전 3에 비해 1.301GiB 만큼 감소했음을 보여줍니다.
readFiles.func1
의 도움말은 1.045GiB 감소를 보여줍니다.가비지 컬렉션에 미치는 영향을 수량화하려면 CPU 시간 프로필 비교를 구성합니다. 다음 스크린샷에서는 Go 가비지 수집기
runtime.gcBgMarkWorker.*
의 스택을 보여주는 필터가 적용됩니다. 스크린샷은 가비지 컬렉션의 CPU 사용량이 16.8%에서 4.97%로 감소했음을 보여줍니다.애플리케이션에서 처리하는 초당 요청 수의 변경에 따른 영향이 있는지 확인하려면 Cloud Shell에서 출력을 확인합니다. 이 예시에서 버전 4는 초당 최대 15개의 요청을 완료하며, 이는 버전 3의 초당 요청 5.8개보다 훨씬 높습니다.
$ 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 ...
애플리케이션에서 제공하는 초당 쿼리 수가 늘어나는 것은 가비지 컬렉션 소요 시간이 줄어들기 때문일 수 있습니다.
힙 프로필을 보면
readFiles
수정의 영향을 더 잘 이해할 수 있습니다. 버전 4와 버전 3의 힙 프로필을 비교해 보면 힙 사용량이 70.95MiB에서 18.47MiB로 감소했음을 알 수 있습니다.
요약
이 빠른 시작에서는 CPU 시간 및 할당된 힙 프로필을 사용하여 애플리케이션의 잠재적 최적화를 파악했습니다. 초당 요청 수를 극대화하고 불필요한 할당을 제거하는 것이 목표였습니다.
CPU 시간 프로필을 사용하여 CPU를 많이 사용하는 함수를 식별했습니다. 간단한 변경사항을 적용한 후 서버의 요청 속도는 초당 약 1회에서 초당 5.8로 증가했습니다.
할당된 힙 프로필을 사용하여
shakesapp/server.go
함수readFiles
가 할당 비율이 높은 것으로 확인되었습니다.readFiles
를 최적화한 후 서버의 요청 속도는 초당 요청 15개로 증가했고, 10초 프로필 수집 중에 할당된 평균 메모리 양은 1.301GiB 만큼 감소했습니다.
다음 단계
Cloud Profiler 에이전트 실행 방법에 대한 자세한 내용은 다음을 참조하세요.- Go 애플리케이션 프로파일링
- 자바 애플리케이션 프로파일링
- Node.js 애플리케이션 프로파일링
- Python 애플리케이션 프로파일링
- Google Cloud 외부에서 실행되는 애플리케이션 프로파일링