Skip to content

Commit 1a39dbb

Browse files
committed
Add BenchmarkProfilerGoroutines and update README
1 parent 546f82f commit 1a39dbb

File tree

3 files changed

+72
-1
lines changed

3 files changed

+72
-1
lines changed

BenchmarkProfilerGoroutines.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
$ go test -bench=BenchmarkProfilerGoroutines
2+
goos: darwin
3+
goarch: amd64
4+
pkg: github.com/felixge/fgprof
5+
BenchmarkProfilerGoroutines/1_goroutines-8 43431 26860 ns/op
6+
BenchmarkProfilerGoroutines/2_goroutines-8 42590 27648 ns/op
7+
BenchmarkProfilerGoroutines/4_goroutines-8 40725 28694 ns/op
8+
BenchmarkProfilerGoroutines/8_goroutines-8 37874 31067 ns/op
9+
BenchmarkProfilerGoroutines/16_goroutines-8 32778 37302 ns/op
10+
BenchmarkProfilerGoroutines/32_goroutines-8 25447 47171 ns/op
11+
BenchmarkProfilerGoroutines/64_goroutines-8 17937 66803 ns/op
12+
BenchmarkProfilerGoroutines/128_goroutines-8 11138 108283 ns/op
13+
BenchmarkProfilerGoroutines/256_goroutines-8 5232 191830 ns/op
14+
BenchmarkProfilerGoroutines/512_goroutines-8 2848 351686 ns/op
15+
BenchmarkProfilerGoroutines/1024_goroutines-8 1611 681412 ns/op
16+
BenchmarkProfilerGoroutines/2048_goroutines-8 846 1396125 ns/op
17+
BenchmarkProfilerGoroutines/4096_goroutines-8 358 3286943 ns/op
18+
BenchmarkProfilerGoroutines/8192_goroutines-8 153 7813804 ns/op
19+
BenchmarkProfilerGoroutines/16384_goroutines-8 70 16440643 ns/op
20+
BenchmarkProfilerGoroutines/32768_goroutines-8 33 34101649 ns/op
21+
BenchmarkProfilerGoroutines/65536_goroutines-8 16 68460458 ns/op
22+
BenchmarkProfilerGoroutines/131072_goroutines-8 8 134481118 ns/op
23+
BenchmarkProfilerGoroutines/262144_goroutines-8 4 270522885 ns/op
24+
BenchmarkProfilerGoroutines/524288_goroutines-8 2 567821104 ns/op
25+
BenchmarkProfilerGoroutines/1048576_goroutines-8 1 1202184643 ns/op

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ fgprof is implemented as a background goroutine that wakes up 99 times per secon
175175

176176
This data is used to maintain an in-memory stack counter which can be converted to the pprof or folded output format. The meat of the implementation is super simple and < 100 lines of code, you should [check it out](./fgprof.go).
177177

178-
Generally speaking, fgprof should not have a big impact on the performance of your program. However `runtime.GoroutineProfile` calls `stopTheWorld()` and could be slow if you have a lot of goroutines. For now the advise is to test the impact of the profiler on a development environment before running it against production instances. In the future this README will try to provide a more detailed analysis of the performance impact.
178+
The overhead of fgprof increases with the number of active goroutines (including those waiting on I/O, Channels, Locks, etc.) executed by your program. If your program typically has less than 1000 active goroutines, you shouldn't have much to worry about. However, at 10k or more goroutines fgprof is likely to become unable to maintain its sampling rate and to significantly degrade the performance of your application, see [BenchmarkProfilerGoroutines.txt](./BenchmarkProfilerGoroutines.txt). The latter is due to `runtime.GoroutineProfile()` calling `stopTheWorld()`. For now the advise is to test the impact of the profiler on a development environment before running it against production instances. There are ideas for making fgprof more scalable and safe for programs with a high number of goroutines, but they will likely need improved APIs from the Go core.
179179

180180
### Go's builtin CPU Profiler
181181

fgprof_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package fgprof
22

33
import (
44
"bytes"
5+
"fmt"
56
"strings"
67
"testing"
78
"time"
@@ -31,6 +32,51 @@ func BenchmarkProfiler(b *testing.B) {
3132
}
3233
}
3334

35+
func BenchmarkProfilerGoroutines(b *testing.B) {
36+
for g := 1; g <= 1024*1024; g = g * 2 {
37+
g := g
38+
name := fmt.Sprintf("%d goroutines", g)
39+
40+
b.Run(name, func(b *testing.B) {
41+
prof := &profiler{}
42+
initalRoutines := len(prof.GoroutineProfile())
43+
44+
readyCh := make(chan struct{})
45+
stopCh := make(chan struct{})
46+
for i := 0; i < g; i++ {
47+
go func() {
48+
defer func() { stopCh <- struct{}{} }()
49+
readyCh <- struct{}{}
50+
}()
51+
<-readyCh
52+
}
53+
54+
b.ResetTimer()
55+
for i := 0; i < b.N; i++ {
56+
stacks := prof.GoroutineProfile()
57+
gotRoutines := len(stacks) - initalRoutines
58+
if gotRoutines != g {
59+
b.Logf("want %d goroutines, but got %d on iteration %d", g, len(stacks), i)
60+
}
61+
}
62+
b.StopTimer()
63+
for i := 0; i < g; i++ {
64+
<-stopCh
65+
}
66+
start := time.Now()
67+
for i := 0; ; i++ {
68+
if len(prof.GoroutineProfile()) == initalRoutines {
69+
break
70+
}
71+
time.Sleep(20 * time.Millisecond)
72+
if time.Since(start) > 10*time.Second {
73+
b.Fatalf("%d goroutines still running, want %d", len(prof.GoroutineProfile()), initalRoutines)
74+
}
75+
}
76+
})
77+
}
78+
}
79+
3480
func BenchmarkStackCounter(b *testing.B) {
3581
prof := &profiler{}
3682
stacks := prof.GoroutineProfile()

0 commit comments

Comments
 (0)