Skip to content

Latest commit

 

History

History
286 lines (203 loc) · 9.66 KB

191-199.md

File metadata and controls

286 lines (203 loc) · 9.66 KB

191-199

벤치마킹

기본 벤치마크

벤치마크 함수가 있는 <file_name>_test.go 파일로 벤치마크를 할 수 있다. SprintSprintf 중 어느 것이 더 성능이 좋고, 효율적으로 자원을 할당하는지 벤치마크 해볼 텐데, Sprint가 문자열 포맷을 하는 동안 오버헤드가 없기 때문에 더 나을 것으로 보이지만 그렇지 않다. 추측보다는 실제 확인한 결과로 판단하자.

package main

import (
    "fmt"
    "testing"
)

var gs string

BenchmarkSprintBasicSprint 성능을 테스트한다. 벤치마크 하려는 모든 코드는 b.N 반복문 안에 있어야 한다. 벤치마크 도구가 처음 호출 할 때 b.N은 1이고, 충분히 테스트가 진행될 때 까지 b.N값은 지속적으로 증가한다. 전역변수 gsfmt.Sprint의 값을 반환하기 때문에 dead code 처럼 보이지 않게 된다.

func BenchmarkSprintBasic(b *testing.B) {
    var s string

    for i := 0; i < b.N; i++ {
        s = fmt.Sprint("hello")
    }
    gs = s
}

BenchmarkSprintfBasicSprintf의 성능을 테스트한다.

func BenchmarkSprintfBasic(b *testing.B) {
    var s string
    for i := 0; i < b.N; i++ {
        s = fmt.Sprintf("hello")
    }
    gs = s
}

go test -run none -bench . -benchtime 3s -benchmem

goos: darwin
goarch: amd64
BenchmarkSprintBasic-16     52997451    56.9 ns/op    5 B/op    1 allocs/op
BenchmarkSprintfBasic-16    72737234    45.7 ns/op    5 B/op    1 allocs/op
PASS
ok  command-line-arguments  6.637s

하위 벤치마크

하위 테스트를 하듯이, 하위 벤치마크도 가능하다.

package main

import (
    "fmt"
    "testing"
)

BenchmarkSprintSubSprint와 관련한 모든 하위 벤치마크를 한다.

func BenchmarkSprintSub(b *testing.B) {
    b.Run("none", benchSprint)
    b.Run("format", benchSprintf)
}

benchSprintSprint의 성능을 테스트한다.

func benchSprint(b *testing.B) {
    var s string

    for i := 0; i < b.N; i++ {
        s = fmt.Sprint("hello")
    }
    gs = s
}

benchSprintfSprintf의 성능을 테스트한다.

func benchSprintf(b *testing.B) {
    var s string

    for i := 0; i < b.N; i++ {
        s = fmt.Sprintf("hello")
    }
    gs = s
}

go test -run none -bench . -benchtime 3s -benchmem

goos: darwin
goarch: amd64
BenchmarkSprintSub/none-16    54088082    60.6 ns/op    5 B/op    1 allocs/op
BenchmarkSprintSub/format-16  67906119    52.3 ns/op    5 B/op    1 allocs/op
PASS
ok command-line-arguments 7.131s

하위 벤치마크를 할 수 있는 다른 방법들:

  • go test -run none -bench BenchmarkSprintSub/none -benchtime 3s -benchmem
  • go test -run none -bench BenchmarkSprintSub/format -benchtime 3s -benchmem

프로파일링(Profiling)

스택 트레이스(Stack Trace)

스택 트레이스 리뷰(Review Stack Trace)

어떻게 스택 트레이스를 할 수 있을까?

package main

func main() {

이 예제는 배열, 문자열, 정수를 사용한다. 길이 2, 용량 4의 배열을 만들고 그 배열 값을 예제 함수에 전달한다.

    example(make([]string, 2, 4), "hello", 10)
}

예제는 내장 함수 panic을 호출하여 스택 트레이스을 보여준다.

func example(slice []string, str string, i int) {
    panic("Want stack trace")
}

예제를 통해 출력되는 결과:

panic: Want stack trace

goroutine 1 [running]:
main.example(0xc420053f38, 0x2, 0x4, 0x1066c02, 0x5, 0xa)

/Users/hoanhan/go/src/github.com/hoanhan101/ultimate-go/go/profiling/stack_trace.go:18 +0x39
main.main()

/Users/hoanhan/go/src/github.com/hoanhan101/ultimate-go/go/profiling/stack_trace.go:13 +0x72
exit status 2

컴파일러는 어떤 라인에서 문제가 생겼는지 알려준다. 스택 트레이스를 사용했을 때 더 좋은 점은, 함수에 전달되는 값을 정확히 알 수 있다. 스택 트레이스는 데이터 구조를 펼쳐서 표시한다. slice는 3개의 word로 표시 되는데, 첫 번째 word는 포인터, 두 번째 word는 2(길이), 세 번째 word는 4(용량)이다. string은 2개의 word로 표시 되는데, "hello"는 길이가 5인 5byte의 문자열이고, 첫 번째 word는 포인터, 두 번째 word는 5(길이)이다. 마지막으로 남은 word는 10(정수)이다. 스택 트레이스에서 볼 수 있는 main.example (0xc420053f38, 0x2, 0x4, 0x1066c02, 0x5, 0xa)에서 각각에 해당되는 값은 포인터 주소, 2, 4, 포인터 주소, 5, a(16진수로 표기된 10) 이다.

스택 트레이스를 사용하는 것 만으로도 사용된 값을 확인하거나 필요한 데이터를 얻을 수 있다. Dave의 error 패키지를 사용하고, 몇몇 컨텍스트를 더하거나 log 패키지를 추가하면 디버깅 할 때 문제에 대한 더 많은 정보를 얻을 수 있다.

Packing

값을 채우는 스택 트레이스의 예.

example 함수에 할당되는 값들은 전부 1byte이다.

package main

func main() {
    example(true, false, true, 25)
}

func example(b1, b2, b3 bool, i uint8) {
    panic("Want stack trace")
}

출력 결과:

panic: Want stack trace

goroutine 1 [running]:
main.example(0xc419010001)

/Users/hoanhan/go/src/github.com/hoanhan101/ultimate-go/go/profiling/stack_trace_2.go:12 +0x39
main.main()

/Users/hoanhan/go/src/github.com/hoanhan101/ultimate-go/go/profiling/stack_trace_2.go:8 +0x29
exit status 2

이번에는 스택 트레이스가 1 word 밖에 보여주지 않는데, 이 4바이트는 32비트 환경에선 half-wold로, 64비트 환경에선 full-word로 표시된다. 예제에 사용된 시스템은 리틀 엔디언을 사용하고 있기 때문에 오른쪽에서 왼쪽으로 읽어야 한다. 0xc419010001 을 아래와 같이 나타낼 수 있다:

Bits Binary Hex Value
00-07 0000 0001 01 true
08-15 0000 0000 00 false
16-23 0000 0001 01 true
24-31 0001 1001 19 25

GODEBUG

메모리 트레이싱

메모리 트레이싱은 코드가 실행될 때 GC 및 힙 메모리에 관련해서 잘 작동하는지 분석을 제공한다. 다음은 메모리 릭을 일으키는 예제이다.

package main

import (
    "os"
    "os/signal"
)

func main() {

아래 코드는 고루틴을 생성할 때 메모리 릭을 일으킨다. 코드가 종료될 때 까지 계속 Key-value를 할당한다.

    go func() {
        m := make(map[int]int)
        for i := 0; ; i++ {
            m[i] = i
        }
    }()

Ctrl-C 로 코드를 종료한다.

    sig := make(chan os.Signal, 1)
    signal.Notify(sig)
    <-sig
}

GODEBUG 라는 환경변수를 사용하게 되면 메모리와 스케줄러를 확인할 수 있게 된다. 빌드 및 실행 방법:

빌드 : go build memory_tracing.go

실행 : GODEBUG=gctrace=1 ./memory_tracing

GODEBUG=gctrace=1을 설정하면 가비지 컬렉터는 각 수집마다 수집된 메모리와 실행 시간을 요약하여 한줄씩 출력한다.

문제가 발생한 부분을 찾는 방법:

gc {0} @{1}s {2}%: {3}+...+{4} ms clock, {5}+...+{6} ms cpu, {7}->{8}->{9} MB, {10} MB goal, {11} P

의미: {0} : gc 실행 횟수 {1} : 프로그램이 실행 된 시간. {2} : gc가 차지하는 CPU의 비율. {3} : 프로그램 실행 시간 - 프로그램의 지연시간이나 리소스를 사용할 수 있을 때까지 대기하는 시간을 포함한 실시간 측정 값. {4} : 프로그램 실행 시간. 보기보다 중요한 숫자. {5} : CPU 클럭 {6} : CPU 클럭 {7} : gc가 시작되기 전의 힙 크기. {8} : gc 실행 후 힙 크기. {9} : 라이브 힙의 크기. {10} : gc의 목표, 페이싱 알고리즘. {11} : 프로세스 수.

실행 결과:

gc 1 @0.007s 0%: 0.010+0.13+0.030 ms clock, 0.080+0/0.058/0.15+0.24 ms cpu, 5->5->3 MB, 6 MB goal, 8 P
gc 2 @0.013s 0%: 0.003+0.21+0.034 ms clock, 0.031+0/0.030/0.22+0.27 ms cpu, 9->9->7 MB, 10 MB goal, 8 P
gc 3 @0.029s 0%: 0.003+0.23+0.030 ms clock, 0.029+0.050/0.016/0.25+0.24 ms cpu, 18->18->15 MB, 19 MB goal, 8 P
gc 4 @0.062s 0%: 0.003+0.40+0.040 ms clock, 0.030+0/0.28/0.11+0.32 ms cpu, 36->36->30 MB, 37 MB goal, 8 P
gc 5 @0.135s 0%: 0.003+0.63+0.045 ms clock, 0.027+0/0.026/0.64+0.36 ms cpu, 72->72->60 MB, 73 MB goal, 8 P
gc 6 @0.302s 0%: 0.003+0.98+0.043 ms clock, 0.031+0.078/0.016/0.88+0.34 ms cpu, 65->66->42 MB, 120 MB goal, 8 P
gc 7 @0.317s 0%: 0.003+1.2+0.080 ms clock, 0.026+0/1.1/0.13+0.64 ms cpu, 120->121->120 MB, 121 MB goal, 8 P
gc 8 @0.685s 0%: 0.004+1.6+0.041 ms clock, 0.032+0/1.5/0.72+0.33 ms cpu, 288->288->241 MB, 289 MB goal, 8 P
gc 9 @1.424s 0%: 0.004+4.0+0.081 ms clock, 0.033+0.027/3.8/0.53+0.65 ms cpu, 577->577->482 MB, 578 MB goal, 8 P
gc 10 @2.592s 0%: 0.003+11+0.045 ms clock, 0.031+0/5.9/5.2+0.36 ms cpu, 499->499->317 MB, 964 MB goal, 8 P

처음에는 빠른데, 점점 느려지기 시작한다. GC가 실행될 때 마다 힙 사이즈가 증가하는 부분에서 메모리 릭이 발생함을 알 수 있다.

소통하기

당신이 The Ultimate Go Book을 잘 이해할 수 있었는지 이메일(hoanhan101@gmail.com)로 알려주시길 바랍니다.

만약, 당신이 나의 활동 및 프로젝트, 그리고 더 나은 소프트웨어 엔지니어가 되는데 관심이 있다면, 내 웹사이트 https://hoanhan101.github.io/ 에 자유롭게 방문해주시길 바랍니다.

읽어주셔서 고맙습니다. 행운을 빕니다!