From 97d573c34404f405698da7c1ed591d362aa3edad Mon Sep 17 00:00:00 2001 From: irfan sharif Date: Thu, 12 May 2022 19:38:57 -0400 Subject: [PATCH] Add tests for proportionality Evaluate measured cputime using the tests from github.com/golang/go/issues/36821, which demonstrate the inaccuracy and imprecision of go's 100Hz cpu profiler. The results here suggest that measured cputime is both accurate and precise with regards to computing on-CPU time. === RUN TestEquivalentGoroutines 0's got 9.98% of total time 1's got 9.53% of total time 2's got 9.22% of total time 3's got 10.42% of total time 4's got 9.84% of total time 5's got 10.43% of total time 6's got 10.50% of total time 7's got 10.21% of total time 8's got 10.03% of total time 9's got 9.86% of total time === RUN TestProportionalGoroutines 0's got 1.87% of total time (1.000000x) 1's got 3.60% of total time (1.931999x) 2's got 5.41% of total time (2.899312x) 3's got 7.21% of total time (3.864451x) 4's got 9.11% of total time (4.880925x) 5's got 10.94% of total time (5.864723x) 6's got 12.77% of total time (6.842004x) 7's got 14.34% of total time (7.685840x) 8's got 16.58% of total time (8.885060x) 9's got 18.18% of total time (9.741030x) --- runtime_test.go | 100 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/runtime_test.go b/runtime_test.go index 9512d3d..943a7ee 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -67,6 +67,106 @@ func TestInternalDuration(t *testing.T) { } } +// TestEquivalentGoroutines is a variant of the parallel test in +// https://github.com/golang/go/issues/36821. +func TestEquivalentGoroutines(t *testing.T) { + mu := struct { + sync.Mutex + nanos map[int]int64 + }{} + mu.nanos = make(map[int]int64) + + f := func(wg *sync.WaitGroup, id int) { + defer wg.Done() + + var sum int + for i := 0; i < 500000000; i++ { + sum -= i / 2 + sum *= i + sum /= i/3 + 1 + sum -= i / 4 + } + + nanos := grunningnanos() + mu.Lock() + mu.nanos[id] = nanos + mu.Unlock() + } + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + i := i // copy loop variable + wg.Add(1) + go f(&wg, i) + } + wg.Wait() + + mu.Lock() + defer mu.Unlock() + + total := int64(0) + for _, nanos := range mu.nanos { + total += nanos + } + + minexp, maxexp := float64(0.085), float64(0.115) + for i, nanos := range mu.nanos { + got := float64(nanos) / float64(total) + + assert.Greaterf(t, got, minexp, + "expected proportion > %f, got %f", minexp, got) + assert.Lessf(t, got, maxexp, + "expected proportion < %f, got %f", maxexp, got) + + t.Logf("%d's got %0.2f%% of total time", i, got*100) + } +} + +// TestProportionalGoroutines is a variant of the serial test in +// https://github.com/golang/go/issues/36821. +func TestProportionalGoroutines(t *testing.T) { + f := func(wg *sync.WaitGroup, v uint64, trip uint64, result *int64) { + defer wg.Done() + + ret := v + for i := trip; i > 0; i-- { + ret += i + ret = ret ^ (i + 0xcafebabe) + } + + nanos := grunningnanos() + atomic.AddInt64(result, nanos) + } + + results := make([]int64, 10, 10) + var wg sync.WaitGroup + + for iters := 0; iters < 10000; iters++ { + for i := uint64(0); i < 10; i++ { + i := i // copy loop variable + wg.Add(1) + go f(&wg, i+1, (i+1)*100000, &results[i]) + } + } + + wg.Wait() + + total := int64(0) + for _, result := range results { + total += result + } + + initial := float64(results[0]) / float64(total) + maxdelta := float64(0.5) + for i, result := range results { + got := float64(result) / float64(total) + mult := got / initial + assert.InDelta(t, float64(i+1), mult, maxdelta) + + t.Logf("%d's got %0.2f%% of total time (%fx)", i, got*100, mult) + } +} + // BenchmarkMetricNanos measures how costly it is to read the current // goroutine's running time when going through the exported runtime metric. func BenchmarkMetricNanos(b *testing.B) {