From cd0a1964b502be98f51b0bdbf512c84ba803f0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCmer=20Cip?= Date: Thu, 10 Dec 2020 02:34:54 -0500 Subject: [PATCH 1/3] preallocate p.stacks() before calling runtime.GoroutineProfile() --- fgprof.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fgprof.go b/fgprof.go index 31871c8..b1d1ca2 100644 --- a/fgprof.go +++ b/fgprof.go @@ -75,6 +75,11 @@ func (p *profiler) GoroutineProfile() []runtime.StackRecord { // TODO(fg) There might be workloads where it would be nice to shrink // p.stacks dynamically as well, but let's not over-engineer this until we // understand those cases better. + + nGoroutines := runtime.NumGoroutine() + if len(p.stacks) < nGoroutines { + p.stacks = make([]runtime.StackRecord, int(float64(nGoroutines)*1.1)) + } for { n, ok := runtime.GoroutineProfile(p.stacks) if !ok { From 2cb57ab0ac3f2b6b30bdcd2099c7b9060d7203f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCmer=20Cip?= Date: Tue, 15 Dec 2020 09:56:17 -0500 Subject: [PATCH 2/3] tryout newProfiler() func --- fgprof.go | 19 +++++++++++++------ fgprof_test.go | 13 ++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/fgprof.go b/fgprof.go index b1d1ca2..3e26ecc 100644 --- a/fgprof.go +++ b/fgprof.go @@ -20,7 +20,7 @@ func Start(w io.Writer, format Format) func() error { ticker := time.NewTicker(time.Second / hz) stopCh := make(chan struct{}) - prof := &profiler{} + prof := newProfiler() stackCounts := stackCounter{} go func() { @@ -50,6 +50,17 @@ type profiler struct { selfFrame *runtime.Frame } +// newProfiler returns a new profiler with a pre-allocated stacks field. This +// is slightly faster than discovering its size during the first +// GoroutineProfile() call. +func newProfiler() *profiler { + n := runtime.NumGoroutine() + return &profiler{ + stacks: make([]runtime.StackRecord, int(float64(n)*1.1)), + } + //return &profiler{} +} + // GoroutineProfile returns the stacks of all goroutines currently managed by // the scheduler. This includes both goroutines that are currently running // (On-CPU), as well as waiting (Off-CPU). @@ -75,14 +86,10 @@ func (p *profiler) GoroutineProfile() []runtime.StackRecord { // TODO(fg) There might be workloads where it would be nice to shrink // p.stacks dynamically as well, but let's not over-engineer this until we // understand those cases better. - - nGoroutines := runtime.NumGoroutine() - if len(p.stacks) < nGoroutines { - p.stacks = make([]runtime.StackRecord, int(float64(nGoroutines)*1.1)) - } for { n, ok := runtime.GoroutineProfile(p.stacks) if !ok { + //fmt.Printf("realloc\n") p.stacks = make([]runtime.StackRecord, int(float64(n)*1.1)) } else { return p.stacks[0:n] diff --git a/fgprof_test.go b/fgprof_test.go index 658ca42..b4f1aab 100644 --- a/fgprof_test.go +++ b/fgprof_test.go @@ -3,6 +3,7 @@ package fgprof import ( "bytes" "fmt" + "runtime" "strings" "testing" "time" @@ -26,20 +27,19 @@ func TestStart(t *testing.T) { } func BenchmarkProfiler(b *testing.B) { - prof := &profiler{} + prof := newProfiler() for i := 0; i < b.N; i++ { prof.GoroutineProfile() } } func BenchmarkProfilerGoroutines(b *testing.B) { - for g := 1; g <= 1024*1024; g = g * 2 { + for g := 10000; g <= 10000; g = g * 2 { g := g name := fmt.Sprintf("%d goroutines", g) b.Run(name, func(b *testing.B) { - prof := &profiler{} - initalRoutines := len(prof.GoroutineProfile()) + initalRoutines := runtime.NumGoroutine() readyCh := make(chan struct{}) stopCh := make(chan struct{}) @@ -51,6 +51,9 @@ func BenchmarkProfilerGoroutines(b *testing.B) { <-readyCh } + // allocate profiler after some goroutines has been generated + prof := newProfiler() + b.ResetTimer() for i := 0; i < b.N; i++ { stacks := prof.GoroutineProfile() @@ -78,7 +81,7 @@ func BenchmarkProfilerGoroutines(b *testing.B) { } func BenchmarkStackCounter(b *testing.B) { - prof := &profiler{} + prof := newProfiler() stacks := prof.GoroutineProfile() sc := stackCounter{} b.ResetTimer() From ca73dfe90e5c8b7bbce0453f43a9def53a339553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCmer=20Cip?= Date: Tue, 15 Dec 2020 10:10:15 -0500 Subject: [PATCH 3/3] tidy --- fgprof.go | 2 -- fgprof_test.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/fgprof.go b/fgprof.go index 3e26ecc..9940d4a 100644 --- a/fgprof.go +++ b/fgprof.go @@ -58,7 +58,6 @@ func newProfiler() *profiler { return &profiler{ stacks: make([]runtime.StackRecord, int(float64(n)*1.1)), } - //return &profiler{} } // GoroutineProfile returns the stacks of all goroutines currently managed by @@ -89,7 +88,6 @@ func (p *profiler) GoroutineProfile() []runtime.StackRecord { for { n, ok := runtime.GoroutineProfile(p.stacks) if !ok { - //fmt.Printf("realloc\n") p.stacks = make([]runtime.StackRecord, int(float64(n)*1.1)) } else { return p.stacks[0:n] diff --git a/fgprof_test.go b/fgprof_test.go index b4f1aab..e847b82 100644 --- a/fgprof_test.go +++ b/fgprof_test.go @@ -34,7 +34,7 @@ func BenchmarkProfiler(b *testing.B) { } func BenchmarkProfilerGoroutines(b *testing.B) { - for g := 10000; g <= 10000; g = g * 2 { + for g := 1; g <= 1024*1024; g = g * 2 { g := g name := fmt.Sprintf("%d goroutines", g)