]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: convert flaky semaphore linearity test into benchmark
authorMichael Anthony Knyszek <mknyszek@google.com>
Fri, 17 Jun 2022 19:56:27 +0000 (19:56 +0000)
committerGopher Robot <gobot@golang.org>
Tue, 21 Jun 2022 19:37:22 +0000 (19:37 +0000)
Also, add a benchmark for another case that was originally tested.

Also also, remove all the dead code this now creates.

Fixes #53428.

Change-Id: Idbba88d3d31d38a8854fd5ed99001e394da27300
Reviewed-on: https://go-review.googlesource.com/c/go/+/412878
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>

src/go/build/deps_test.go
src/internal/testenv/testenv.go
src/internal/testmath/bench.go [deleted file]
src/internal/testmath/ttest.go [deleted file]
src/runtime/sema.go
src/runtime/sema_test.go

index 1ddf8f69be2f23f180235712f640cc24e2faf395..5b971b93e20803a2eaf90a2c293a9d56437b16a0 100644 (file)
@@ -543,10 +543,7 @@ var depsRules = `
        internal/fuzz, internal/testlog, runtime/pprof, regexp
        < testing/internal/testdeps;
 
-       MATH, errors, testing
-       < internal/testmath;
-
-       OS, flag, testing, internal/cfg, internal/testmath
+       OS, flag, testing, internal/cfg
        < internal/testenv;
 
        OS, encoding/base64
index b7cb95063b21dd3f5046e16db2d67a4e69ec8c7d..1feb630cf5fa38e137c0e1a9c1267d1a79b37dbd 100644 (file)
@@ -16,7 +16,6 @@ import (
        "flag"
        "fmt"
        "internal/cfg"
-       "internal/testmath"
        "os"
        "os/exec"
        "path/filepath"
@@ -464,67 +463,3 @@ func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
 
        return b.Bytes(), err
 }
-
-// CheckLinear checks if the function produced by f scales linearly.
-//
-// f must accept a scale factor which causes the input to the function it
-// produces to scale by that factor.
-func CheckLinear(t *testing.T, f func(scale float64) func(*testing.B)) {
-       MustHaveExec(t)
-
-       if os.Getenv("GO_PERF_UNIT_TEST") == "" {
-               // Invoke the same test as a subprocess with the GO_PERF_UNIT_TEST environment variable set.
-               // We create a subprocess for two reasons:
-               //
-               //   1. There's no other way to set the benchmarking parameters of testing.Benchmark.
-               //   2. Since we're effectively running a performance test, running in a subprocess grants
-               //      us a little bit more isolation than using the same process.
-               //
-               // As an alternative, we could fairly easily reimplement the timing code in testing.Benchmark,
-               // but a subprocess is just as easy to create.
-
-               selfCmd := CleanCmdEnv(exec.Command(os.Args[0], "-test.v", fmt.Sprintf("-test.run=^%s$", t.Name()), "-test.benchtime=1x"))
-               selfCmd.Env = append(selfCmd.Env, "GO_PERF_UNIT_TEST=1")
-               output, err := RunWithTimeout(t, selfCmd)
-               if err != nil {
-                       t.Error(err)
-                       t.Logf("--- subprocess output ---\n%s", string(output))
-               }
-               if bytes.Contains(output, []byte("insignificant result")) {
-                       t.Skip("insignificant result")
-               }
-               return
-       }
-
-       // Pick a reasonable sample count.
-       const count = 10
-
-       // Collect samples for scale factor 1.
-       x1 := make([]testing.BenchmarkResult, 0, count)
-       for i := 0; i < count; i++ {
-               x1 = append(x1, testing.Benchmark(f(1.0)))
-       }
-
-       // Collect samples for scale factor 2.
-       x2 := make([]testing.BenchmarkResult, 0, count)
-       for i := 0; i < count; i++ {
-               x2 = append(x2, testing.Benchmark(f(2.0)))
-       }
-
-       // Run a t-test on the results.
-       r1 := testmath.BenchmarkResults(x1)
-       r2 := testmath.BenchmarkResults(x2)
-       result, err := testmath.TwoSampleWelchTTest(r1, r2, testmath.LocationDiffers)
-       if err != nil {
-               t.Fatalf("failed to run t-test: %v", err)
-       }
-       if result.P > 0.005 {
-               // Insignificant result.
-               t.Skip("insignificant result")
-       }
-
-       // Let ourselves be within 3x; 2x is too strict.
-       if m1, m2 := r1.Mean(), r2.Mean(); 3.0*m1 < m2 {
-               t.Fatalf("failure to scale linearly: µ_1=%s µ_2=%s p=%f", time.Duration(m1), time.Duration(m2), result.P)
-       }
-}
diff --git a/src/internal/testmath/bench.go b/src/internal/testmath/bench.go
deleted file mode 100644 (file)
index 6f034b4..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package testmath
-
-import (
-       "math"
-       "testing"
-       "time"
-)
-
-type BenchmarkResults []testing.BenchmarkResult
-
-func (b BenchmarkResults) Weight() float64 {
-       var weight int
-       for _, r := range b {
-               weight += r.N
-       }
-       return float64(weight)
-}
-
-func (b BenchmarkResults) Mean() float64 {
-       var dur time.Duration
-       for _, r := range b {
-               dur += r.T * time.Duration(r.N)
-       }
-       return float64(dur) / b.Weight()
-}
-
-func (b BenchmarkResults) Variance() float64 {
-       var num float64
-       mean := b.Mean()
-       for _, r := range b {
-               num += math.Pow(float64(r.T)-mean, 2) * float64(r.N)
-       }
-       return float64(num) / b.Weight()
-}
diff --git a/src/internal/testmath/ttest.go b/src/internal/testmath/ttest.go
deleted file mode 100644 (file)
index d15d2de..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package testmath
-
-import (
-       "errors"
-       "math"
-)
-
-// A TTestSample is a sample that can be used for a one or two sample
-// t-test.
-type TTestSample interface {
-       Weight() float64
-       Mean() float64
-       Variance() float64
-}
-
-var (
-       ErrSampleSize        = errors.New("sample is too small")
-       ErrZeroVariance      = errors.New("sample has zero variance")
-       ErrMismatchedSamples = errors.New("samples have different lengths")
-)
-
-// TwoSampleWelchTTest performs a two-sample (unpaired) Welch's t-test
-// on samples x1 and x2. This t-test does not assume the distributions
-// have equal variance.
-func TwoSampleWelchTTest(x1, x2 TTestSample, alt LocationHypothesis) (*TTestResult, error) {
-       n1, n2 := x1.Weight(), x2.Weight()
-       if n1 <= 1 || n2 <= 1 {
-               // TODO: Can we still do this with n == 1?
-               return nil, ErrSampleSize
-       }
-       v1, v2 := x1.Variance(), x2.Variance()
-       if v1 == 0 && v2 == 0 {
-               return nil, ErrZeroVariance
-       }
-
-       dof := math.Pow(v1/n1+v2/n2, 2) /
-               (math.Pow(v1/n1, 2)/(n1-1) + math.Pow(v2/n2, 2)/(n2-1))
-       s := math.Sqrt(v1/n1 + v2/n2)
-       t := (x1.Mean() - x2.Mean()) / s
-       return newTTestResult(int(n1), int(n2), t, dof, alt), nil
-}
-
-// A TTestResult is the result of a t-test.
-type TTestResult struct {
-       // N1 and N2 are the sizes of the input samples. For a
-       // one-sample t-test, N2 is 0.
-       N1, N2 int
-
-       // T is the value of the t-statistic for this t-test.
-       T float64
-
-       // DoF is the degrees of freedom for this t-test.
-       DoF float64
-
-       // AltHypothesis specifies the alternative hypothesis tested
-       // by this test against the null hypothesis that there is no
-       // difference in the means of the samples.
-       AltHypothesis LocationHypothesis
-
-       // P is p-value for this t-test for the given null hypothesis.
-       P float64
-}
-
-func newTTestResult(n1, n2 int, t, dof float64, alt LocationHypothesis) *TTestResult {
-       dist := TDist{dof}
-       var p float64
-       switch alt {
-       case LocationDiffers:
-               p = 2 * (1 - dist.CDF(math.Abs(t)))
-       case LocationLess:
-               p = dist.CDF(t)
-       case LocationGreater:
-               p = 1 - dist.CDF(t)
-       }
-       return &TTestResult{N1: n1, N2: n2, T: t, DoF: dof, AltHypothesis: alt, P: p}
-}
-
-// A LocationHypothesis specifies the alternative hypothesis of a
-// location test such as a t-test or a Mann-Whitney U-test. The
-// default (zero) value is to test against the alternative hypothesis
-// that they differ.
-type LocationHypothesis int
-
-const (
-       // LocationLess specifies the alternative hypothesis that the
-       // location of the first sample is less than the second. This
-       // is a one-tailed test.
-       LocationLess LocationHypothesis = -1
-
-       // LocationDiffers specifies the alternative hypothesis that
-       // the locations of the two samples are not equal. This is a
-       // two-tailed test.
-       LocationDiffers LocationHypothesis = 0
-
-       // LocationGreater specifies the alternative hypothesis that
-       // the location of the first sample is greater than the
-       // second. This is a one-tailed test.
-       LocationGreater LocationHypothesis = 1
-)
-
-// A TDist is a Student's t-distribution with V degrees of freedom.
-type TDist struct {
-       V float64
-}
-
-// PDF returns the value at x of the probability distribution function for the
-// distribution.
-func (t TDist) PDF(x float64) float64 {
-       return math.Exp(lgamma((t.V+1)/2)-lgamma(t.V/2)) /
-               math.Sqrt(t.V*math.Pi) * math.Pow(1+(x*x)/t.V, -(t.V+1)/2)
-}
-
-// CDF returns the value at x of the cumulative distribution function for the
-// distribution.
-func (t TDist) CDF(x float64) float64 {
-       if x == 0 {
-               return 0.5
-       } else if x > 0 {
-               return 1 - 0.5*betaInc(t.V/(t.V+x*x), t.V/2, 0.5)
-       } else if x < 0 {
-               return 1 - t.CDF(-x)
-       } else {
-               return math.NaN()
-       }
-}
-
-func (t TDist) Bounds() (float64, float64) {
-       return -4, 4
-}
-
-func lgamma(x float64) float64 {
-       y, _ := math.Lgamma(x)
-       return y
-}
-
-// betaInc returns the value of the regularized incomplete beta
-// function Iₓ(a, b) = 1 / B(a, b) * ∫₀ˣ tᵃ⁻¹ (1-t)ᵇ⁻¹ dt.
-//
-// This is not to be confused with the "incomplete beta function",
-// which can be computed as BetaInc(x, a, b)*Beta(a, b).
-//
-// If x < 0 or x > 1, returns NaN.
-func betaInc(x, a, b float64) float64 {
-       // Based on Numerical Recipes in C, section 6.4. This uses the
-       // continued fraction definition of I:
-       //
-       //  (xᵃ*(1-x)ᵇ)/(a*B(a,b)) * (1/(1+(d₁/(1+(d₂/(1+...))))))
-       //
-       // where B(a,b) is the beta function and
-       //
-       //  d_{2m+1} = -(a+m)(a+b+m)x/((a+2m)(a+2m+1))
-       //  d_{2m}   = m(b-m)x/((a+2m-1)(a+2m))
-       if x < 0 || x > 1 {
-               return math.NaN()
-       }
-       bt := 0.0
-       if 0 < x && x < 1 {
-               // Compute the coefficient before the continued
-               // fraction.
-               bt = math.Exp(lgamma(a+b) - lgamma(a) - lgamma(b) +
-                       a*math.Log(x) + b*math.Log(1-x))
-       }
-       if x < (a+1)/(a+b+2) {
-               // Compute continued fraction directly.
-               return bt * betacf(x, a, b) / a
-       } else {
-               // Compute continued fraction after symmetry transform.
-               return 1 - bt*betacf(1-x, b, a)/b
-       }
-}
-
-// betacf is the continued fraction component of the regularized
-// incomplete beta function Iₓ(a, b).
-func betacf(x, a, b float64) float64 {
-       const maxIterations = 200
-       const epsilon = 3e-14
-
-       raiseZero := func(z float64) float64 {
-               if math.Abs(z) < math.SmallestNonzeroFloat64 {
-                       return math.SmallestNonzeroFloat64
-               }
-               return z
-       }
-
-       c := 1.0
-       d := 1 / raiseZero(1-(a+b)*x/(a+1))
-       h := d
-       for m := 1; m <= maxIterations; m++ {
-               mf := float64(m)
-
-               // Even step of the recurrence.
-               numer := mf * (b - mf) * x / ((a + 2*mf - 1) * (a + 2*mf))
-               d = 1 / raiseZero(1+numer*d)
-               c = raiseZero(1 + numer/c)
-               h *= d * c
-
-               // Odd step of the recurrence.
-               numer = -(a + mf) * (a + b + mf) * x / ((a + 2*mf) * (a + 2*mf + 1))
-               d = 1 / raiseZero(1+numer*d)
-               c = raiseZero(1 + numer/c)
-               hfac := d * c
-               h *= hfac
-
-               if math.Abs(hfac-1) < epsilon {
-                       return h
-               }
-       }
-       panic("betainc: a or b too big; failed to converge")
-}
index c7a1a76e280339f5db9ef1ad31e378afa63e8628..39935f70a9e5488df915eec41d78410dbd49cc33 100644 (file)
@@ -35,8 +35,8 @@ import (
 // where n is the number of distinct addresses with goroutines blocked
 // on them that hash to the given semaRoot.
 // See golang.org/issue/17953 for a program that worked badly
-// before we introduced the second level of list, and TestSemTableOneAddrCollisionLinear
-// for a test that exercises this.
+// before we introduced the second level of list, and
+// BenchmarkSemTable/OneAddrCollision/* for a benchmark that exercises this.
 type semaRoot struct {
        lock  mutex
        treap *sudog // root of balanced tree of unique waiters.
index f3e95d10beb863a498a99e5786039783a77e0302..9943d2ed39ce787fd14eba0e85775c78126f424d 100644 (file)
@@ -5,7 +5,7 @@
 package runtime_test
 
 import (
-       "internal/testenv"
+       "fmt"
        . "runtime"
        "sync"
        "sync/atomic"
@@ -103,45 +103,68 @@ func testSemaHandoff() bool {
        return res == 1 // did the waiter run first?
 }
 
-func TestSemTableOneAddrCollisionLinear(t *testing.T) {
-       testenv.CheckLinear(t, func(scale float64) func(*testing.B) {
-               n := int(1000 * scale)
-               return func(b *testing.B) {
+func BenchmarkSemTable(b *testing.B) {
+       for _, n := range []int{1000, 2000, 4000, 8000} {
+               b.Run(fmt.Sprintf("OneAddrCollision/n=%d", n), func(b *testing.B) {
                        tab := Escape(new(SemTable))
                        u := make([]uint32, SemTableSize+1)
 
                        b.ResetTimer()
 
-                       // Simulate two locks colliding on the same semaRoot.
-                       //
-                       // Specifically enqueue all the waiters for the first lock,
-                       // then all the waiters for the second lock.
-                       //
-                       // Then, dequeue all the waiters from the first lock, then
-                       // the second.
-                       //
-                       // Each enqueue/dequeue operation should be O(1), because
-                       // there are exactly 2 locks. This could be O(n) if all
-                       // the waiters for both locks are on the same list, as it
-                       // once was.
-                       for i := 0; i < n; i++ {
-                               if i < n/2 {
-                                       tab.Enqueue(&u[0])
-                               } else {
-                                       tab.Enqueue(&u[SemTableSize])
+                       for j := 0; j < b.N; j++ {
+                               // Simulate two locks colliding on the same semaRoot.
+                               //
+                               // Specifically enqueue all the waiters for the first lock,
+                               // then all the waiters for the second lock.
+                               //
+                               // Then, dequeue all the waiters from the first lock, then
+                               // the second.
+                               //
+                               // Each enqueue/dequeue operation should be O(1), because
+                               // there are exactly 2 locks. This could be O(n) if all
+                               // the waiters for both locks are on the same list, as it
+                               // once was.
+                               for i := 0; i < n; i++ {
+                                       if i < n/2 {
+                                               tab.Enqueue(&u[0])
+                                       } else {
+                                               tab.Enqueue(&u[SemTableSize])
+                                       }
+                               }
+                               for i := 0; i < n; i++ {
+                                       var ok bool
+                                       if i < n/2 {
+                                               ok = tab.Dequeue(&u[0])
+                                       } else {
+                                               ok = tab.Dequeue(&u[SemTableSize])
+                                       }
+                                       if !ok {
+                                               b.Fatal("failed to dequeue")
+                                       }
                                }
                        }
-                       for i := 0; i < n; i++ {
-                               var ok bool
-                               if i < n/2 {
-                                       ok = tab.Dequeue(&u[0])
-                               } else {
-                                       ok = tab.Dequeue(&u[SemTableSize])
+               })
+               b.Run(fmt.Sprintf("ManyAddrCollision/n=%d", n), func(b *testing.B) {
+                       tab := Escape(new(SemTable))
+                       u := make([]uint32, n*SemTableSize)
+
+                       b.ResetTimer()
+
+                       for j := 0; j < b.N; j++ {
+                               // Simulate n locks colliding on the same semaRoot.
+                               //
+                               // Each enqueue/dequeue operation should be O(log n), because
+                               // each semaRoot is a tree. This could be O(n) if it was
+                               // some simpler data structure.
+                               for i := 0; i < n; i++ {
+                                       tab.Enqueue(&u[i*SemTableSize])
                                }
-                               if !ok {
-                                       b.Fatal("failed to dequeue")
+                               for i := 0; i < n; i++ {
+                                       if !tab.Dequeue(&u[i*SemTableSize]) {
+                                               b.Fatal("failed to dequeue")
+                                       }
                                }
                        }
-               }
-       })
+               })
+       }
 }