Revert "Copy code from https://github.com/golang/perf/tree/master/internal/stats"

This reverts commit 9a045f1d04a74d0723a642847bde4e02c02aa9f0.

Reason for revert: Found the upstream code at github.com/aclements/go-moremath so there's no need to do this copy.

Original change's description:
> Copy code from https://github.com/golang/perf/tree/master/internal/stats
>
> Because we can't import an 'internal' directory.
>
> Since that code has been stable for close to four years, this seems relatively
> safe.
>
> The code is copyright the Go authors using the Go License. See the added LICENSE
> file.
>
> The only changes are:
>   1. Add unittest.SmallTest()
>   2. gofmt -s -w .
>
> Bug: skia:10884
> Change-Id: I8d1d922686762a9799cc7ed2d57867c498ce93ab
> Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/331369
> Reviewed-by: Kevin Lubick <kjlubick@google.com>
> Commit-Queue: Joe Gregorio <jcgregorio@google.com>

TBR=jcgregorio@google.com,kjlubick@google.com

Change-Id: I305c17737bf6696a3669fe3fd4e7ce6b6b389cc2
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:10884
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/331556
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/LICENSE b/perf/LICENSE
deleted file mode 100644
index ea5ea89..0000000
--- a/perf/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
-   * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/perf/go/stats/README.md b/perf/go/stats/README.md
deleted file mode 100644
index 6349ebd..0000000
--- a/perf/go/stats/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# stats
-
-This code is copied from
-https://github.com/golang/perf/tree/master/internal/stats because we can't
-import an 'internal' directory.
-
-Since that code has been stable for close to four years, this seems relatively
-safe.
-
-The code is copyright the Go authors using the Go License. See the local LICENSE
-file.
diff --git a/perf/go/stats/alg.go b/perf/go/stats/alg.go
deleted file mode 100644
index f3774b5..0000000
--- a/perf/go/stats/alg.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2015 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 stats
-
-// Miscellaneous helper algorithms
-
-import (
-	"fmt"
-)
-
-func maxint(a, b int) int {
-	if a > b {
-		return a
-	}
-	return b
-}
-
-func minint(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-func sumint(xs []int) int {
-	sum := 0
-	for _, x := range xs {
-		sum += x
-	}
-	return sum
-}
-
-// bisect returns an x in [low, high] such that |f(x)| <= tolerance
-// using the bisection method.
-//
-// f(low) and f(high) must have opposite signs.
-//
-// If f does not have a root in this interval (e.g., it is
-// discontiguous), this returns the X of the apparent discontinuity
-// and false.
-func bisect(f func(float64) float64, low, high, tolerance float64) (float64, bool) {
-	flow, fhigh := f(low), f(high)
-	if -tolerance <= flow && flow <= tolerance {
-		return low, true
-	}
-	if -tolerance <= fhigh && fhigh <= tolerance {
-		return high, true
-	}
-	if mathSign(flow) == mathSign(fhigh) {
-		panic(fmt.Sprintf("root of f is not bracketed by [low, high]; f(%g)=%g f(%g)=%g", low, flow, high, fhigh))
-	}
-	for {
-		mid := (high + low) / 2
-		fmid := f(mid)
-		if -tolerance <= fmid && fmid <= tolerance {
-			return mid, true
-		}
-		if mid == high || mid == low {
-			return mid, false
-		}
-		if mathSign(fmid) == mathSign(flow) {
-			low = mid
-			flow = fmid
-		} else {
-			high = mid
-			fhigh = fmid
-		}
-	}
-}
-
-// bisectBool implements the bisection method on a boolean function.
-// It returns x1, x2 ∈ [low, high], x1 < x2 such that f(x1) != f(x2)
-// and x2 - x1 <= xtol.
-//
-// If f(low) == f(high), it panics.
-func bisectBool(f func(float64) bool, low, high, xtol float64) (x1, x2 float64) {
-	flow, fhigh := f(low), f(high)
-	if flow == fhigh {
-		panic(fmt.Sprintf("root of f is not bracketed by [low, high]; f(%g)=%v f(%g)=%v", low, flow, high, fhigh))
-	}
-	for {
-		if high-low <= xtol {
-			return low, high
-		}
-		mid := (high + low) / 2
-		if mid == high || mid == low {
-			return low, high
-		}
-		fmid := f(mid)
-		if fmid == flow {
-			low = mid
-			flow = fmid
-		} else {
-			high = mid
-			fhigh = fmid
-		}
-	}
-}
-
-// series returns the sum of the series f(0), f(1), ...
-//
-// This implementation is fast, but subject to round-off error.
-func series(f func(float64) float64) float64 {
-	y, yp := 0.0, 1.0
-	for n := 0.0; y != yp; n++ {
-		yp = y
-		y += f(n)
-	}
-	return y
-}
diff --git a/perf/go/stats/beta.go b/perf/go/stats/beta.go
deleted file mode 100644
index 9cd1cd7..0000000
--- a/perf/go/stats/beta.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2015 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 stats
-
-import "math"
-
-func lgamma(x float64) float64 {
-	y, _ := math.Lgamma(x)
-	return y
-}
-
-// mathBeta returns the value of the complete beta function B(a, b).
-func mathBeta(a, b float64) float64 {
-	// B(x,y) = Γ(x)Γ(y) / Γ(x+y)
-	return math.Exp(lgamma(a) + lgamma(b) - lgamma(a+b))
-}
-
-// mathBetaInc returns the value of the regularized incomplete beta
-// function Iₓ(a, b).
-//
-// 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 mathBetaInc(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")
-}
diff --git a/perf/go/stats/beta_test.go b/perf/go/stats/beta_test.go
deleted file mode 100644
index 760380f..0000000
--- a/perf/go/stats/beta_test.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestBetaInc(t *testing.T) {
-	unittest.SmallTest(t)
-	// Example values from MATLAB betainc documentation.
-	testFunc(t, "I_0.5(%v, 3)",
-		func(a float64) float64 { return mathBetaInc(0.5, a, 3) },
-		map[float64]float64{
-			0:  1.00000000000000,
-			1:  0.87500000000000,
-			2:  0.68750000000000,
-			3:  0.50000000000000,
-			4:  0.34375000000000,
-			5:  0.22656250000000,
-			6:  0.14453125000000,
-			7:  0.08984375000000,
-			8:  0.05468750000000,
-			9:  0.03271484375000,
-			10: 0.01928710937500,
-		})
-}
diff --git a/perf/go/stats/deltadist.go b/perf/go/stats/deltadist.go
deleted file mode 100644
index bb3ba3f..0000000
--- a/perf/go/stats/deltadist.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2015 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 stats
-
-// DeltaDist is the Dirac delta function, centered at T, with total
-// area 1.
-//
-// The CDF of the Dirac delta function is the Heaviside step function,
-// centered at T. Specifically, f(T) == 1.
-type DeltaDist struct {
-	T float64
-}
-
-func (d DeltaDist) PDF(x float64) float64 {
-	if x == d.T {
-		return inf
-	}
-	return 0
-}
-
-func (d DeltaDist) pdfEach(xs []float64) []float64 {
-	res := make([]float64, len(xs))
-	for i, x := range xs {
-		if x == d.T {
-			res[i] = inf
-		}
-	}
-	return res
-}
-
-func (d DeltaDist) CDF(x float64) float64 {
-	if x >= d.T {
-		return 1
-	}
-	return 0
-}
-
-func (d DeltaDist) cdfEach(xs []float64) []float64 {
-	res := make([]float64, len(xs))
-	for i, x := range xs {
-		res[i] = d.CDF(x)
-	}
-	return res
-}
-
-func (d DeltaDist) InvCDF(y float64) float64 {
-	if y < 0 || y > 1 {
-		return nan
-	}
-	return d.T
-}
-
-func (d DeltaDist) Bounds() (float64, float64) {
-	return d.T - 1, d.T + 1
-}
diff --git a/perf/go/stats/dist.go b/perf/go/stats/dist.go
deleted file mode 100644
index 048477d..0000000
--- a/perf/go/stats/dist.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2015 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 stats
-
-import "math/rand"
-
-// A DistCommon is a statistical distribution. DistCommon is a base
-// interface provided by both continuous and discrete distributions.
-type DistCommon interface {
-	// CDF returns the cumulative probability Pr[X <= x].
-	//
-	// For continuous distributions, the CDF is the integral of
-	// the PDF from -inf to x.
-	//
-	// For discrete distributions, the CDF is the sum of the PMF
-	// at all defined points from -inf to x, inclusive. Note that
-	// the CDF of a discrete distribution is defined for the whole
-	// real line (unlike the PMF) but has discontinuities where
-	// the PMF is non-zero.
-	//
-	// The CDF is a monotonically increasing function and has a
-	// domain of all real numbers. If the distribution has bounded
-	// support, it has a range of [0, 1]; otherwise it has a range
-	// of (0, 1). Finally, CDF(-inf)==0 and CDF(inf)==1.
-	CDF(x float64) float64
-
-	// Bounds returns reasonable bounds for this distribution's
-	// PDF/PMF and CDF. The total weight outside of these bounds
-	// should be approximately 0.
-	//
-	// For a discrete distribution, both bounds are integer
-	// multiples of Step().
-	//
-	// If this distribution has finite support, it returns exact
-	// bounds l, h such that CDF(l')=0 for all l' < l and
-	// CDF(h')=1 for all h' >= h.
-	Bounds() (float64, float64)
-}
-
-// A Dist is a continuous statistical distribution.
-type Dist interface {
-	DistCommon
-
-	// PDF returns the value of the probability density function
-	// of this distribution at x.
-	PDF(x float64) float64
-}
-
-// A DiscreteDist is a discrete statistical distribution.
-//
-// Most discrete distributions are defined only at integral values of
-// the random variable. However, some are defined at other intervals,
-// so this interface takes a float64 value for the random variable.
-// The probability mass function rounds down to the nearest defined
-// point. Note that float64 values can exactly represent integer
-// values between ±2**53, so this generally shouldn't be an issue for
-// integer-valued distributions (likewise, for half-integer-valued
-// distributions, float64 can exactly represent all values between
-// ±2**52).
-type DiscreteDist interface {
-	DistCommon
-
-	// PMF returns the value of the probability mass function
-	// Pr[X = x'], where x' is x rounded down to the nearest
-	// defined point on the distribution.
-	//
-	// Note for implementers: for integer-valued distributions,
-	// round x using int(math.Floor(x)). Do not use int(x), since
-	// that truncates toward zero (unless all x <= 0 are handled
-	// the same).
-	PMF(x float64) float64
-
-	// Step returns s, where the distribution is defined for sℕ.
-	Step() float64
-}
-
-// TODO: Add a Support method for finite support distributions? Or
-// maybe just another return value from Bounds indicating that the
-// bounds are exact?
-
-// TODO: Plot method to return a pre-configured Plot object with
-// reasonable bounds and an integral function? Have to distinguish
-// PDF/CDF/InvCDF. Three methods? Argument?
-//
-// Doesn't have to be a method of Dist. Could be just a function that
-// takes a Dist and uses Bounds.
-
-// InvCDF returns the inverse CDF function of the given distribution
-// (also known as the quantile function or the percent point
-// function). This is a function f such that f(dist.CDF(x)) == x. If
-// dist.CDF is only weakly monotonic (that it, there are intervals
-// over which it is constant) and y > 0, f returns the smallest x that
-// satisfies this condition. In general, the inverse CDF is not
-// well-defined for y==0, but for convenience if y==0, f returns the
-// largest x that satisfies this condition. For distributions with
-// infinite support both the largest and smallest x are -Inf; however,
-// for distributions with finite support, this is the lower bound of
-// the support.
-//
-// If y < 0 or y > 1, f returns NaN.
-//
-// If dist implements InvCDF(float64) float64, this returns that
-// method. Otherwise, it returns a function that uses a generic
-// numerical method to construct the inverse CDF at y by finding x
-// such that dist.CDF(x) == y. This may have poor precision around
-// points of discontinuity, including f(0) and f(1).
-func InvCDF(dist DistCommon) func(y float64) (x float64) {
-	type invCDF interface {
-		InvCDF(float64) float64
-	}
-	if dist, ok := dist.(invCDF); ok {
-		return dist.InvCDF
-	}
-
-	// Otherwise, use a numerical algorithm.
-	//
-	// TODO: For discrete distributions, use the step size to
-	// inform this computation.
-	return func(y float64) (x float64) {
-		const almostInf = 1e100
-		const xtol = 1e-16
-
-		if y < 0 || y > 1 {
-			return nan
-		} else if y == 0 {
-			l, _ := dist.Bounds()
-			if dist.CDF(l) == 0 {
-				// Finite support
-				return l
-			} else {
-				// Infinite support
-				return -inf
-			}
-		} else if y == 1 {
-			_, h := dist.Bounds()
-			if dist.CDF(h) == 1 {
-				// Finite support
-				return h
-			} else {
-				// Infinite support
-				return inf
-			}
-		}
-
-		// Find loX, hiX for which cdf(loX) < y <= cdf(hiX).
-		var loX, loY, hiX, hiY float64
-		x1, y1 := 0.0, dist.CDF(0)
-		xdelta := 1.0
-		if y1 < y {
-			hiX, hiY = x1, y1
-			for hiY < y && hiX != inf {
-				loX, loY, hiX = hiX, hiY, hiX+xdelta
-				hiY = dist.CDF(hiX)
-				xdelta *= 2
-			}
-		} else {
-			loX, loY = x1, y1
-			for y <= loY && loX != -inf {
-				hiX, hiY, loX = loX, loY, loX-xdelta
-				loY = dist.CDF(loX)
-				xdelta *= 2
-			}
-		}
-		if loX == -inf {
-			return loX
-		} else if hiX == inf {
-			return hiX
-		}
-
-		// Use bisection on the interval to find the smallest
-		// x at which cdf(x) <= y.
-		_, x = bisectBool(func(x float64) bool {
-			return dist.CDF(x) < y
-		}, loX, hiX, xtol)
-		return
-	}
-}
-
-// Rand returns a random number generator that draws from the given
-// distribution. The returned generator takes an optional source of
-// randomness; if this is nil, it uses the default global source.
-//
-// If dist implements Rand(*rand.Rand) float64, Rand returns that
-// method. Otherwise, it returns a generic generator based on dist's
-// inverse CDF (which may in turn use an efficient implementation or a
-// generic numerical implementation; see InvCDF).
-func Rand(dist DistCommon) func(*rand.Rand) float64 {
-	type distRand interface {
-		Rand(*rand.Rand) float64
-	}
-	if dist, ok := dist.(distRand); ok {
-		return dist.Rand
-	}
-
-	// Otherwise, use a generic algorithm.
-	inv := InvCDF(dist)
-	return func(r *rand.Rand) float64 {
-		var y float64
-		for y == 0 {
-			if r == nil {
-				y = rand.Float64()
-			} else {
-				y = r.Float64()
-			}
-		}
-		return inv(y)
-	}
-}
diff --git a/perf/go/stats/mathx.go b/perf/go/stats/mathx.go
deleted file mode 100644
index 68a9a0a..0000000
--- a/perf/go/stats/mathx.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2015 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 stats
-
-import "math"
-
-// mathSign returns the sign of x: -1 if x < 0, 0 if x == 0, 1 if x > 0.
-// If x is NaN, it returns NaN.
-func mathSign(x float64) float64 {
-	if x == 0 {
-		return 0
-	} else if x < 0 {
-		return -1
-	} else if x > 0 {
-		return 1
-	}
-	return nan
-}
-
-const smallFactLimit = 20 // 20! => 62 bits
-var smallFact [smallFactLimit + 1]int64
-
-func init() {
-	smallFact[0] = 1
-	fact := int64(1)
-	for n := int64(1); n <= smallFactLimit; n++ {
-		fact *= n
-		smallFact[n] = fact
-	}
-}
-
-// mathChoose returns the binomial coefficient of n and k.
-func mathChoose(n, k int) float64 {
-	if k == 0 || k == n {
-		return 1
-	}
-	if k < 0 || n < k {
-		return 0
-	}
-	if n <= smallFactLimit { // Implies k <= smallFactLimit
-		// It's faster to do several integer multiplications
-		// than it is to do an extra integer division.
-		// Remarkably, this is also faster than pre-computing
-		// Pascal's triangle (presumably because this is very
-		// cache efficient).
-		numer := int64(1)
-		for n1 := int64(n - (k - 1)); n1 <= int64(n); n1++ {
-			numer *= n1
-		}
-		denom := smallFact[k]
-		return float64(numer / denom)
-	}
-
-	return math.Exp(lchoose(n, k))
-}
-
-// mathLchoose returns math.Log(mathChoose(n, k)).
-func mathLchoose(n, k int) float64 {
-	if k == 0 || k == n {
-		return 0
-	}
-	if k < 0 || n < k {
-		return math.NaN()
-	}
-	return lchoose(n, k)
-}
-
-func lchoose(n, k int) float64 {
-	a, _ := math.Lgamma(float64(n + 1))
-	b, _ := math.Lgamma(float64(k + 1))
-	c, _ := math.Lgamma(float64(n - k + 1))
-	return a - b - c
-}
diff --git a/perf/go/stats/normaldist.go b/perf/go/stats/normaldist.go
deleted file mode 100644
index d00f96a..0000000
--- a/perf/go/stats/normaldist.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"math"
-	"math/rand"
-)
-
-// NormalDist is a normal (Gaussian) distribution with mean Mu and
-// standard deviation Sigma.
-type NormalDist struct {
-	Mu, Sigma float64
-}
-
-// StdNormal is the standard normal distribution (Mu = 0, Sigma = 1)
-var StdNormal = NormalDist{0, 1}
-
-// 1/sqrt(2 * pi)
-const invSqrt2Pi = 0.39894228040143267793994605993438186847585863116493465766592583
-
-func (n NormalDist) PDF(x float64) float64 {
-	z := x - n.Mu
-	return math.Exp(-z*z/(2*n.Sigma*n.Sigma)) * invSqrt2Pi / n.Sigma
-}
-
-func (n NormalDist) pdfEach(xs []float64) []float64 {
-	res := make([]float64, len(xs))
-	if n.Mu == 0 && n.Sigma == 1 {
-		// Standard normal fast path
-		for i, x := range xs {
-			res[i] = math.Exp(-x*x/2) * invSqrt2Pi
-		}
-	} else {
-		a := -1 / (2 * n.Sigma * n.Sigma)
-		b := invSqrt2Pi / n.Sigma
-		for i, x := range xs {
-			z := x - n.Mu
-			res[i] = math.Exp(z*z*a) * b
-		}
-	}
-	return res
-}
-
-func (n NormalDist) CDF(x float64) float64 {
-	return math.Erfc(-(x-n.Mu)/(n.Sigma*math.Sqrt2)) / 2
-}
-
-func (n NormalDist) cdfEach(xs []float64) []float64 {
-	res := make([]float64, len(xs))
-	a := 1 / (n.Sigma * math.Sqrt2)
-	for i, x := range xs {
-		res[i] = math.Erfc(-(x-n.Mu)*a) / 2
-	}
-	return res
-}
-
-func (n NormalDist) InvCDF(p float64) (x float64) {
-	// This is based on Peter John Acklam's inverse normal CDF
-	// algorithm: http://home.online.no/~pjacklam/notes/invnorm/
-	const (
-		a1 = -3.969683028665376e+01
-		a2 = 2.209460984245205e+02
-		a3 = -2.759285104469687e+02
-		a4 = 1.383577518672690e+02
-		a5 = -3.066479806614716e+01
-		a6 = 2.506628277459239e+00
-
-		b1 = -5.447609879822406e+01
-		b2 = 1.615858368580409e+02
-		b3 = -1.556989798598866e+02
-		b4 = 6.680131188771972e+01
-		b5 = -1.328068155288572e+01
-
-		c1 = -7.784894002430293e-03
-		c2 = -3.223964580411365e-01
-		c3 = -2.400758277161838e+00
-		c4 = -2.549732539343734e+00
-		c5 = 4.374664141464968e+00
-		c6 = 2.938163982698783e+00
-
-		d1 = 7.784695709041462e-03
-		d2 = 3.224671290700398e-01
-		d3 = 2.445134137142996e+00
-		d4 = 3.754408661907416e+00
-
-		plow  = 0.02425
-		phigh = 1 - plow
-	)
-
-	if p < 0 || p > 1 {
-		return nan
-	} else if p == 0 {
-		return -inf
-	} else if p == 1 {
-		return inf
-	}
-
-	if p < plow {
-		// Rational approximation for lower region.
-		q := math.Sqrt(-2 * math.Log(p))
-		x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) /
-			((((d1*q+d2)*q+d3)*q+d4)*q + 1)
-	} else if phigh < p {
-		// Rational approximation for upper region.
-		q := math.Sqrt(-2 * math.Log(1-p))
-		x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q + c6) /
-			((((d1*q+d2)*q+d3)*q+d4)*q + 1)
-	} else {
-		// Rational approximation for central region.
-		q := p - 0.5
-		r := q * q
-		x = (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r + a6) * q /
-			(((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r + 1)
-	}
-
-	// Refine approximation.
-	e := 0.5*math.Erfc(-x/math.Sqrt2) - p
-	u := e * math.Sqrt(2*math.Pi) * math.Exp(x*x/2)
-	x = x - u/(1+x*u/2)
-
-	// Adjust from standard normal.
-	return x*n.Sigma + n.Mu
-}
-
-func (n NormalDist) Rand(r *rand.Rand) float64 {
-	var x float64
-	if r == nil {
-		x = rand.NormFloat64()
-	} else {
-		x = r.NormFloat64()
-	}
-	return x*n.Sigma + n.Mu
-}
-
-func (n NormalDist) Bounds() (float64, float64) {
-	const stddevs = 3
-	return n.Mu - stddevs*n.Sigma, n.Mu + stddevs*n.Sigma
-}
diff --git a/perf/go/stats/normaldist_test.go b/perf/go/stats/normaldist_test.go
deleted file mode 100644
index 1e83c32..0000000
--- a/perf/go/stats/normaldist_test.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"fmt"
-	"math"
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestNormalDist(t *testing.T) {
-	unittest.SmallTest(t)
-	d := StdNormal
-
-	testFunc(t, fmt.Sprintf("%+v.PDF", d), d.PDF, map[float64]float64{
-		-10000: 0, // approx
-		-1:     1 / math.Sqrt(2*math.Pi) * math.Exp(-0.5),
-		0:      1 / math.Sqrt(2*math.Pi),
-		1:      1 / math.Sqrt(2*math.Pi) * math.Exp(-0.5),
-		10000:  0, // approx
-	})
-
-	testFunc(t, fmt.Sprintf("%+v.CDF", d), d.CDF, map[float64]float64{
-		-10000: 0, // approx
-		0:      0.5,
-		10000:  1, // approx
-	})
-
-	d2 := NormalDist{Mu: 2, Sigma: 5}
-	testInvCDF(t, d, false)
-	testInvCDF(t, d2, false)
-}
diff --git a/perf/go/stats/package.go b/perf/go/stats/package.go
deleted file mode 100644
index d0b291e..0000000
--- a/perf/go/stats/package.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2015 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 stats implements several statistical distributions,
-// hypothesis tests, and functions for descriptive statistics.
-//
-// Currently stats is fairly small, but for what it does implement, it
-// focuses on high quality, fast implementations with good, idiomatic
-// Go APIs.
-//
-// This is a trimmed fork of github.com/aclements/go-moremath/stats.
-package stats
-
-import (
-	"errors"
-	"math"
-)
-
-var inf = math.Inf(1)
-var nan = math.NaN()
-
-// TODO: Put all errors in the same place and maybe unify them.
-
-var (
-	ErrSamplesEqual = errors.New("all samples are equal")
-)
diff --git a/perf/go/stats/sample.go b/perf/go/stats/sample.go
deleted file mode 100644
index d6fe8a6..0000000
--- a/perf/go/stats/sample.go
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"math"
-	"sort"
-)
-
-// Sample is a collection of possibly weighted data points.
-type Sample struct {
-	// Xs is the slice of sample values.
-	Xs []float64
-
-	// Weights[i] is the weight of sample Xs[i].  If Weights is
-	// nil, all Xs have weight 1.  Weights must have the same
-	// length of Xs and all values must be non-negative.
-	Weights []float64
-
-	// Sorted indicates that Xs is sorted in ascending order.
-	Sorted bool
-}
-
-// Bounds returns the minimum and maximum values of xs.
-func Bounds(xs []float64) (min float64, max float64) {
-	if len(xs) == 0 {
-		return math.NaN(), math.NaN()
-	}
-	min, max = xs[0], xs[0]
-	for _, x := range xs {
-		if x < min {
-			min = x
-		}
-		if x > max {
-			max = x
-		}
-	}
-	return
-}
-
-// Bounds returns the minimum and maximum values of the Sample.
-//
-// If the Sample is weighted, this ignores samples with zero weight.
-//
-// This is constant time if s.Sorted and there are no zero-weighted
-// values.
-func (s Sample) Bounds() (min float64, max float64) {
-	if len(s.Xs) == 0 || (!s.Sorted && s.Weights == nil) {
-		return Bounds(s.Xs)
-	}
-
-	if s.Sorted {
-		if s.Weights == nil {
-			return s.Xs[0], s.Xs[len(s.Xs)-1]
-		}
-		min, max = math.NaN(), math.NaN()
-		for i, w := range s.Weights {
-			if w != 0 {
-				min = s.Xs[i]
-				break
-			}
-		}
-		if math.IsNaN(min) {
-			return
-		}
-		for i := range s.Weights {
-			if s.Weights[len(s.Weights)-i-1] != 0 {
-				max = s.Xs[len(s.Weights)-i-1]
-				break
-			}
-		}
-	} else {
-		min, max = math.Inf(1), math.Inf(-1)
-		for i, x := range s.Xs {
-			w := s.Weights[i]
-			if x < min && w != 0 {
-				min = x
-			}
-			if x > max && w != 0 {
-				max = x
-			}
-		}
-		if math.IsInf(min, 0) {
-			min, max = math.NaN(), math.NaN()
-		}
-	}
-	return
-}
-
-// vecSum returns the sum of xs.
-func vecSum(xs []float64) float64 {
-	sum := 0.0
-	for _, x := range xs {
-		sum += x
-	}
-	return sum
-}
-
-// Sum returns the (possibly weighted) sum of the Sample.
-func (s Sample) Sum() float64 {
-	if s.Weights == nil {
-		return vecSum(s.Xs)
-	}
-	sum := 0.0
-	for i, x := range s.Xs {
-		sum += x * s.Weights[i]
-	}
-	return sum
-}
-
-// Weight returns the total weight of the Sasmple.
-func (s Sample) Weight() float64 {
-	if s.Weights == nil {
-		return float64(len(s.Xs))
-	}
-	return vecSum(s.Weights)
-}
-
-// Mean returns the arithmetic mean of xs.
-func Mean(xs []float64) float64 {
-	if len(xs) == 0 {
-		return math.NaN()
-	}
-	m := 0.0
-	for i, x := range xs {
-		m += (x - m) / float64(i+1)
-	}
-	return m
-}
-
-// Mean returns the arithmetic mean of the Sample.
-func (s Sample) Mean() float64 {
-	if len(s.Xs) == 0 || s.Weights == nil {
-		return Mean(s.Xs)
-	}
-
-	m, wsum := 0.0, 0.0
-	for i, x := range s.Xs {
-		// Use weighted incremental mean:
-		//   m_i = (1 - w_i/wsum_i) * m_(i-1) + (w_i/wsum_i) * x_i
-		//       = m_(i-1) + (x_i - m_(i-1)) * (w_i/wsum_i)
-		w := s.Weights[i]
-		wsum += w
-		m += (x - m) * w / wsum
-	}
-	return m
-}
-
-// GeoMean returns the geometric mean of xs. xs must be positive.
-func GeoMean(xs []float64) float64 {
-	if len(xs) == 0 {
-		return math.NaN()
-	}
-	m := 0.0
-	for i, x := range xs {
-		if x <= 0 {
-			return math.NaN()
-		}
-		lx := math.Log(x)
-		m += (lx - m) / float64(i+1)
-	}
-	return math.Exp(m)
-}
-
-// GeoMean returns the geometric mean of the Sample. All samples
-// values must be positive.
-func (s Sample) GeoMean() float64 {
-	if len(s.Xs) == 0 || s.Weights == nil {
-		return GeoMean(s.Xs)
-	}
-
-	m, wsum := 0.0, 0.0
-	for i, x := range s.Xs {
-		w := s.Weights[i]
-		wsum += w
-		lx := math.Log(x)
-		m += (lx - m) * w / wsum
-	}
-	return math.Exp(m)
-}
-
-// Variance returns the sample variance of xs.
-func Variance(xs []float64) float64 {
-	if len(xs) == 0 {
-		return math.NaN()
-	} else if len(xs) <= 1 {
-		return 0
-	}
-
-	// Based on Wikipedia's presentation of Welford 1962
-	// (http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm).
-	// This is more numerically stable than the standard two-pass
-	// formula and not prone to massive cancellation.
-	mean, M2 := 0.0, 0.0
-	for n, x := range xs {
-		delta := x - mean
-		mean += delta / float64(n+1)
-		M2 += delta * (x - mean)
-	}
-	return M2 / float64(len(xs)-1)
-}
-
-func (s Sample) Variance() float64 {
-	if len(s.Xs) == 0 || s.Weights == nil {
-		return Variance(s.Xs)
-	}
-	// TODO(austin)
-	panic("Weighted Variance not implemented")
-}
-
-// StdDev returns the sample standard deviation of xs.
-func StdDev(xs []float64) float64 {
-	return math.Sqrt(Variance(xs))
-}
-
-// StdDev returns the sample standard deviation of the Sample.
-func (s Sample) StdDev() float64 {
-	if len(s.Xs) == 0 || s.Weights == nil {
-		return StdDev(s.Xs)
-	}
-	// TODO(austin)
-	panic("Weighted StdDev not implemented")
-}
-
-// Percentile returns the pctileth value from the Sample. This uses
-// interpolation method R8 from Hyndman and Fan (1996).
-//
-// pctile will be capped to the range [0, 1]. If len(xs) == 0 or all
-// weights are 0, returns NaN.
-//
-// Percentile(0.5) is the median. Percentile(0.25) and
-// Percentile(0.75) are the first and third quartiles, respectively.
-//
-// This is constant time if s.Sorted and s.Weights == nil.
-func (s Sample) Percentile(pctile float64) float64 {
-	if len(s.Xs) == 0 {
-		return math.NaN()
-	} else if pctile <= 0 {
-		min, _ := s.Bounds()
-		return min
-	} else if pctile >= 1 {
-		_, max := s.Bounds()
-		return max
-	}
-
-	if !s.Sorted {
-		// TODO(austin) Use select algorithm instead
-		s = *s.Copy().Sort()
-	}
-
-	if s.Weights == nil {
-		N := float64(len(s.Xs))
-		//n := pctile * (N + 1) // R6
-		n := 1/3.0 + pctile*(N+1/3.0) // R8
-		kf, frac := math.Modf(n)
-		k := int(kf)
-		if k <= 0 {
-			return s.Xs[0]
-		} else if k >= len(s.Xs) {
-			return s.Xs[len(s.Xs)-1]
-		}
-		return s.Xs[k-1] + frac*(s.Xs[k]-s.Xs[k-1])
-	} else {
-		// TODO(austin): Implement interpolation
-
-		target := s.Weight() * pctile
-
-		// TODO(austin) If we had cumulative weights, we could
-		// do this in log time.
-		for i, weight := range s.Weights {
-			target -= weight
-			if target < 0 {
-				return s.Xs[i]
-			}
-		}
-		return s.Xs[len(s.Xs)-1]
-	}
-}
-
-// IQR returns the interquartile range of the Sample.
-//
-// This is constant time if s.Sorted and s.Weights == nil.
-func (s Sample) IQR() float64 {
-	if !s.Sorted {
-		s = *s.Copy().Sort()
-	}
-	return s.Percentile(0.75) - s.Percentile(0.25)
-}
-
-type sampleSorter struct {
-	xs      []float64
-	weights []float64
-}
-
-func (p *sampleSorter) Len() int {
-	return len(p.xs)
-}
-
-func (p *sampleSorter) Less(i, j int) bool {
-	return p.xs[i] < p.xs[j]
-}
-
-func (p *sampleSorter) Swap(i, j int) {
-	p.xs[i], p.xs[j] = p.xs[j], p.xs[i]
-	p.weights[i], p.weights[j] = p.weights[j], p.weights[i]
-}
-
-// Sort sorts the samples in place in s and returns s.
-//
-// A sorted sample improves the performance of some algorithms.
-func (s *Sample) Sort() *Sample {
-	if s.Sorted || sort.Float64sAreSorted(s.Xs) {
-		// All set
-	} else if s.Weights == nil {
-		sort.Float64s(s.Xs)
-	} else {
-		sort.Sort(&sampleSorter{s.Xs, s.Weights})
-	}
-	s.Sorted = true
-	return s
-}
-
-// Copy returns a copy of the Sample.
-//
-// The returned Sample shares no data with the original, so they can
-// be modified (for example, sorted) independently.
-func (s Sample) Copy() *Sample {
-	xs := make([]float64, len(s.Xs))
-	copy(xs, s.Xs)
-
-	weights := []float64(nil)
-	if s.Weights != nil {
-		weights = make([]float64, len(s.Weights))
-		copy(weights, s.Weights)
-	}
-
-	return &Sample{xs, weights, s.Sorted}
-}
diff --git a/perf/go/stats/sample_test.go b/perf/go/stats/sample_test.go
deleted file mode 100644
index d7d0118..0000000
--- a/perf/go/stats/sample_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestSamplePercentile(t *testing.T) {
-	unittest.SmallTest(t)
-	s := Sample{Xs: []float64{15, 20, 35, 40, 50}}
-	testFunc(t, "Percentile", s.Percentile, map[float64]float64{
-		-1:  15,
-		0:   15,
-		.05: 15,
-		.30: 19.666666666666666,
-		.40: 27,
-		.95: 50,
-		1:   50,
-		2:   50,
-	})
-}
diff --git a/perf/go/stats/tdist.go b/perf/go/stats/tdist.go
deleted file mode 100644
index 1d1c704..0000000
--- a/perf/go/stats/tdist.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2015 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 stats
-
-import "math"
-
-// A TDist is a Student's t-distribution with V degrees of freedom.
-type TDist struct {
-	V float64
-}
-
-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)
-}
-
-func (t TDist) CDF(x float64) float64 {
-	if x == 0 {
-		return 0.5
-	} else if x > 0 {
-		return 1 - 0.5*mathBetaInc(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
-}
diff --git a/perf/go/stats/tdist_test.go b/perf/go/stats/tdist_test.go
deleted file mode 100644
index 7915631..0000000
--- a/perf/go/stats/tdist_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestT(t *testing.T) {
-	unittest.SmallTest(t)
-	testFunc(t, "PDF(%v|v=1)", TDist{1}.PDF, map[float64]float64{
-		-10: 0.0031515830315226806,
-		-9:  0.0038818278802901312,
-		-8:  0.0048970751720583188,
-		-7:  0.0063661977236758151,
-		-6:  0.0086029698968592104,
-		-5:  0.012242687930145799,
-		-4:  0.018724110951987692,
-		-3:  0.031830988618379075,
-		-2:  0.063661977236758149,
-		-1:  0.15915494309189537,
-		0:   0.31830988618379075,
-		1:   0.15915494309189537,
-		2:   0.063661977236758149,
-		3:   0.031830988618379075,
-		4:   0.018724110951987692,
-		5:   0.012242687930145799,
-		6:   0.0086029698968592104,
-		7:   0.0063661977236758151,
-		8:   0.0048970751720583188,
-		9:   0.0038818278802901312})
-	testFunc(t, "PDF(%v|v=5)", TDist{5}.PDF, map[float64]float64{
-		-10: 4.0989816415343313e-05,
-		-9:  7.4601664362590413e-05,
-		-8:  0.00014444303269563934,
-		-7:  0.00030134402928803911,
-		-6:  0.00068848154013743002,
-		-5:  0.0017574383788078445,
-		-4:  0.0051237270519179133,
-		-3:  0.017292578800222964,
-		-2:  0.065090310326216455,
-		-1:  0.21967979735098059,
-		0:   0.3796066898224944,
-		1:   0.21967979735098059,
-		2:   0.065090310326216455,
-		3:   0.017292578800222964,
-		4:   0.0051237270519179133,
-		5:   0.0017574383788078445,
-		6:   0.00068848154013743002,
-		7:   0.00030134402928803911,
-		8:   0.00014444303269563934,
-		9:   7.4601664362590413e-05})
-
-	testFunc(t, "CDF(%v|v=1)", TDist{1}.CDF, map[float64]float64{
-		-10: 0.03172551743055356,
-		-9:  0.035223287477277272,
-		-8:  0.039583424160565539,
-		-7:  0.045167235300866547,
-		-6:  0.052568456711253424,
-		-5:  0.06283295818900117,
-		-4:  0.077979130377369324,
-		-3:  0.10241638234956672,
-		-2:  0.14758361765043321,
-		-1:  0.24999999999999978,
-		0:   0.5,
-		1:   0.75000000000000022,
-		2:   0.85241638234956674,
-		3:   0.89758361765043326,
-		4:   0.92202086962263075,
-		5:   0.93716704181099886,
-		6:   0.94743154328874657,
-		7:   0.95483276469913347,
-		8:   0.96041657583943452,
-		9:   0.96477671252272279})
-	testFunc(t, "CDF(%v|v=5)", TDist{5}.CDF, map[float64]float64{
-		-10: 8.5473787871481787e-05,
-		-9:  0.00014133998712194845,
-		-8:  0.00024645333028622187,
-		-7:  0.00045837375719920225,
-		-6:  0.00092306914479700695,
-		-5:  0.0020523579900266612,
-		-4:  0.0051617077404157259,
-		-3:  0.015049623948731284,
-		-2:  0.05096973941492914,
-		-1:  0.18160873382456127,
-		0:   0.5,
-		1:   0.81839126617543867,
-		2:   0.9490302605850709,
-		3:   0.98495037605126878,
-		4:   0.99483829225958431,
-		5:   0.99794764200997332,
-		6:   0.99907693085520299,
-		7:   0.99954162624280074,
-		8:   0.99975354666971372,
-		9:   0.9998586600128780})
-}
diff --git a/perf/go/stats/ttest.go b/perf/go/stats/ttest.go
deleted file mode 100644
index 8742298..0000000
--- a/perf/go/stats/ttest.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"errors"
-	"math"
-)
-
-// 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 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")
-)
-
-// TwoSampleTTest performs a two-sample (unpaired) Student's t-test on
-// samples x1 and x2. This is a test of the null hypothesis that x1
-// and x2 are drawn from populations with equal means. It assumes x1
-// and x2 are independent samples, that the distributions have equal
-// variance, and that the populations are normally distributed.
-func TwoSampleTTest(x1, x2 TTestSample, alt LocationHypothesis) (*TTestResult, error) {
-	n1, n2 := x1.Weight(), x2.Weight()
-	if n1 == 0 || n2 == 0 {
-		return nil, ErrSampleSize
-	}
-	v1, v2 := x1.Variance(), x2.Variance()
-	if v1 == 0 && v2 == 0 {
-		return nil, ErrZeroVariance
-	}
-
-	dof := n1 + n2 - 2
-	v12 := ((n1-1)*v1 + (n2-1)*v2) / dof
-	t := (x1.Mean() - x2.Mean()) / math.Sqrt(v12*(1/n1+1/n2))
-	return newTTestResult(int(n1), int(n2), t, dof, alt), nil
-}
-
-// TwoSampleWelchTTest performs a two-sample (unpaired) Welch's t-test
-// on samples x1 and x2. This is like TwoSampleTTest, but 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
-}
-
-// PairedTTest performs a two-sample paired t-test on samples x1 and
-// x2. If μ0 is non-zero, this tests if the average of the difference
-// is significantly different from μ0. If x1 and x2 are identical,
-// this returns nil.
-func PairedTTest(x1, x2 []float64, μ0 float64, alt LocationHypothesis) (*TTestResult, error) {
-	if len(x1) != len(x2) {
-		return nil, ErrMismatchedSamples
-	}
-	if len(x1) <= 1 {
-		// TODO: Can we still do this with n == 1?
-		return nil, ErrSampleSize
-	}
-
-	dof := float64(len(x1) - 1)
-
-	diff := make([]float64, len(x1))
-	for i := range x1 {
-		diff[i] = x1[i] - x2[i]
-	}
-	sd := StdDev(diff)
-	if sd == 0 {
-		// TODO: Can we still do the test?
-		return nil, ErrZeroVariance
-	}
-	t := (Mean(diff) - μ0) * math.Sqrt(float64(len(x1))) / sd
-	return newTTestResult(len(x1), len(x2), t, dof, alt), nil
-}
-
-// OneSampleTTest performs a one-sample t-test on sample x. This tests
-// the null hypothesis that the population mean is equal to μ0. This
-// assumes the distribution of the population of sample means is
-// normal.
-func OneSampleTTest(x TTestSample, μ0 float64, alt LocationHypothesis) (*TTestResult, error) {
-	n, v := x.Weight(), x.Variance()
-	if n == 0 {
-		return nil, ErrSampleSize
-	}
-	if v == 0 {
-		// TODO: Can we still do the test?
-		return nil, ErrZeroVariance
-	}
-	dof := n - 1
-	t := (x.Mean() - μ0) * math.Sqrt(n) / math.Sqrt(v)
-	return newTTestResult(int(n), 0, t, dof, alt), nil
-}
diff --git a/perf/go/stats/ttest_test.go b/perf/go/stats/ttest_test.go
deleted file mode 100644
index 40ffe15..0000000
--- a/perf/go/stats/ttest_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestTTest(t *testing.T) {
-	unittest.SmallTest(t)
-	s1 := Sample{Xs: []float64{2, 1, 3, 4}}
-	s2 := Sample{Xs: []float64{6, 5, 7, 9}}
-
-	check := func(want, got *TTestResult) {
-		if want.N1 != got.N1 || want.N2 != got.N2 ||
-			!aeq(want.T, got.T) || !aeq(want.DoF, got.DoF) ||
-			want.AltHypothesis != got.AltHypothesis ||
-			!aeq(want.P, got.P) {
-			t.Errorf("want %+v, got %+v", want, got)
-		}
-	}
-	check3 := func(test func(alt LocationHypothesis) (*TTestResult, error), n1, n2 int, t, dof float64, pless, pdiff, pgreater float64) {
-		want := &TTestResult{N1: n1, N2: n2, T: t, DoF: dof}
-
-		want.AltHypothesis = LocationLess
-		want.P = pless
-		got, _ := test(want.AltHypothesis)
-		check(want, got)
-
-		want.AltHypothesis = LocationDiffers
-		want.P = pdiff
-		got, _ = test(want.AltHypothesis)
-		check(want, got)
-
-		want.AltHypothesis = LocationGreater
-		want.P = pgreater
-		got, _ = test(want.AltHypothesis)
-		check(want, got)
-	}
-
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return TwoSampleTTest(s1, s1, alt)
-	}, 4, 4, 0, 6,
-		0.5, 1, 0.5)
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return TwoSampleWelchTTest(s1, s1, alt)
-	}, 4, 4, 0, 6,
-		0.5, 1, 0.5)
-
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return TwoSampleTTest(s1, s2, alt)
-	}, 4, 4, -3.9703446152237674, 6,
-		0.0036820296121056195, 0.0073640592242113214, 0.9963179703878944)
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return TwoSampleWelchTTest(s1, s2, alt)
-	}, 4, 4, -3.9703446152237674, 5.584615384615385,
-		0.004256431565689112, 0.0085128631313781695, 0.9957435684343109)
-
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return PairedTTest(s1.Xs, s2.Xs, 0, alt)
-	}, 4, 4, -17, 3,
-		0.0002216717691559955, 0.00044334353831207749, 0.999778328230844)
-
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return OneSampleTTest(s1, 0, alt)
-	}, 4, 0, 3.872983346207417, 3,
-		0.9847668541689145, 0.030466291662170977, 0.015233145831085482)
-	check3(func(alt LocationHypothesis) (*TTestResult, error) {
-		return OneSampleTTest(s1, 2.5, alt)
-	}, 4, 0, 0, 3,
-		0.5, 1, 0.5)
-}
diff --git a/perf/go/stats/udist.go b/perf/go/stats/udist.go
deleted file mode 100644
index 26daa95..0000000
--- a/perf/go/stats/udist.go
+++ /dev/null
@@ -1,387 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"math"
-)
-
-// A UDist is the discrete probability distribution of the
-// Mann-Whitney U statistic for a pair of samples of sizes N1 and N2.
-//
-// The details of computing this distribution with no ties can be
-// found in Mann, Henry B.; Whitney, Donald R. (1947). "On a Test of
-// Whether one of Two Random Variables is Stochastically Larger than
-// the Other". Annals of Mathematical Statistics 18 (1): 50–60.
-// Computing this distribution in the presence of ties is described in
-// Klotz, J. H. (1966). "The Wilcoxon, Ties, and the Computer".
-// Journal of the American Statistical Association 61 (315): 772-787
-// and Cheung, Ying Kuen; Klotz, Jerome H. (1997). "The Mann Whitney
-// Wilcoxon Distribution Using Linked Lists". Statistica Sinica 7:
-// 805-813 (the former paper contains details that are glossed over in
-// the latter paper but has mathematical typesetting issues, so it's
-// easiest to get the context from the former paper and the details
-// from the latter).
-type UDist struct {
-	N1, N2 int
-
-	// T is the count of the number of ties at each rank in the
-	// input distributions. T may be nil, in which case it is
-	// assumed there are no ties (which is equivalent to an M+N
-	// slice of 1s). It must be the case that Sum(T) == M+N.
-	T []int
-}
-
-// hasTies returns true if d has any tied samples.
-func (d UDist) hasTies() bool {
-	for _, t := range d.T {
-		if t > 1 {
-			return true
-		}
-	}
-	return false
-}
-
-// p returns the p_{d.N1,d.N2} function defined by Mann, Whitney 1947
-// for values of U from 0 up to and including the U argument.
-//
-// This algorithm runs in Θ(N1*N2*U) = O(N1²N2²) time and is quite
-// fast for small values of N1 and N2. However, it does not handle ties.
-func (d UDist) p(U int) []float64 {
-	// This is a dynamic programming implementation of the
-	// recursive recurrence definition given by Mann and Whitney:
-	//
-	//   p_{n,m}(U) = (n * p_{n-1,m}(U-m) + m * p_{n,m-1}(U)) / (n+m)
-	//   p_{n,m}(U) = 0                           if U < 0
-	//   p_{0,m}(U) = p{n,0}(U) = 1 / nCr(m+n, n) if U = 0
-	//                          = 0               if U > 0
-	//
-	// (Note that there is a typo in the original paper. The first
-	// recursive application of p should be for U-m, not U-M.)
-	//
-	// Since p{n,m} only depends on p{n-1,m} and p{n,m-1}, we only
-	// need to store one "plane" of the three dimensional space at
-	// a time.
-	//
-	// Furthermore, p_{n,m} = p_{m,n}, so we only construct values
-	// for n <= m and obtain the rest through symmetry.
-	//
-	// We organize the computed values of p as followed:
-	//
-	//       n →   N
-	//     m *
-	//     ↓ * *
-	//       * * *
-	//       * * * *
-	//       * * * *
-	//     M * * * *
-	//
-	// where each * is a slice indexed by U. The code below
-	// computes these left-to-right, top-to-bottom, so it only
-	// stores one row of this matrix at a time. Furthermore,
-	// computing an element in a given U slice only depends on the
-	// same and smaller values of U, so we can overwrite the U
-	// slice we're computing in place as long as we start with the
-	// largest value of U. Finally, even though the recurrence
-	// depends on (n,m) above the diagonal and we use symmetry to
-	// mirror those across the diagonal to (m,n), the mirrored
-	// indexes are always available in the current row, so this
-	// mirroring does not interfere with our ability to recycle
-	// state.
-
-	N, M := d.N1, d.N2
-	if N > M {
-		N, M = M, N
-	}
-
-	memo := make([][]float64, N+1)
-	for n := range memo {
-		memo[n] = make([]float64, U+1)
-	}
-
-	for m := 0; m <= M; m++ {
-		// Compute p_{0,m}. This is zero except for U=0.
-		memo[0][0] = 1
-
-		// Compute the remainder of this row.
-		nlim := N
-		if m < nlim {
-			nlim = m
-		}
-		for n := 1; n <= nlim; n++ {
-			lp := memo[n-1] // p_{n-1,m}
-			var rp []float64
-			if n <= m-1 {
-				rp = memo[n] // p_{n,m-1}
-			} else {
-				rp = memo[m-1] // p{m-1,n} and m==n
-			}
-
-			// For a given n,m, U is at most n*m.
-			//
-			// TODO: Actually, it's at most ⌈n*m/2⌉, but
-			// then we need to use more complex symmetries
-			// in the inner loop below.
-			ulim := n * m
-			if U < ulim {
-				ulim = U
-			}
-
-			out := memo[n] // p_{n,m}
-			nplusm := float64(n + m)
-			for U1 := ulim; U1 >= 0; U1-- {
-				l := 0.0
-				if U1-m >= 0 {
-					l = float64(n) * lp[U1-m]
-				}
-				r := float64(m) * rp[U1]
-				out[U1] = (l + r) / nplusm
-			}
-		}
-	}
-	return memo[N]
-}
-
-type ukey struct {
-	n1   int // size of first sample
-	twoU int // 2*U statistic for this permutation
-}
-
-// This computes the cumulative counts of the Mann-Whitney U
-// distribution in the presence of ties using the computation from
-// Cheung, Ying Kuen; Klotz, Jerome H. (1997). "The Mann Whitney
-// Wilcoxon Distribution Using Linked Lists". Statistica Sinica 7:
-// 805-813, with much guidance from appendix L of Klotz, A
-// Computational Approach to Statistics.
-//
-// makeUmemo constructs a table memo[K][ukey{n1, 2*U}], where K is the
-// number of ranks (up to len(t)), n1 is the size of the first sample
-// (up to the n1 argument), and U is the U statistic (up to the
-// argument twoU/2). The value of an entry in the memo table is the
-// number of permutations of a sample of size n1 in a ranking with tie
-// vector t[:K] having a U statistic <= U.
-func makeUmemo(twoU, n1 int, t []int) []map[ukey]float64 {
-	// Another candidate for a fast implementation is van de Wiel,
-	// "The split-up algorithm: a fast symbolic method for
-	// computing p-values of distribution-free statistics". This
-	// is what's used by R's coin package. It's a comparatively
-	// recent publication, so it's presumably faster (or perhaps
-	// just more general) than previous techniques, but I can't
-	// get my hands on the paper.
-	//
-	// TODO: ~40% of this function's time is spent in mapassign on
-	// the assignment lines in the two loops and another ~20% in
-	// map access and iteration. Improving map behavior or
-	// replacing the maps altogether with some other constant-time
-	// structure could double performance.
-	//
-	// TODO: The worst case for this function is when there are
-	// few ties. Yet the best case overall is when there are *no*
-	// ties. Can we get the best of both worlds? Use the fast
-	// algorithm for the most part when there are few ties and mix
-	// in the general algorithm just where we need it? That's
-	// certainly possible for sub-problems where t[:k] has no
-	// ties, but that doesn't help if t[0] has a tie but nothing
-	// else does. Is it possible to rearrange the ranks without
-	// messing up our computation of the U statistic for
-	// sub-problems?
-
-	K := len(t)
-
-	// Compute a coefficients. The a slice is indexed by k (a[0]
-	// is unused).
-	a := make([]int, K+1)
-	a[1] = t[0]
-	for k := 2; k <= K; k++ {
-		a[k] = a[k-1] + t[k-2] + t[k-1]
-	}
-
-	// Create the memo table for the counts function, A. The A
-	// slice is indexed by k (A[0] is unused).
-	//
-	// In "The Mann Whitney Distribution Using Linked Lists", they
-	// use linked lists (*gasp*) for this, but within each K it's
-	// really just a memoization table, so it's faster to use a
-	// map. The outer structure is a slice indexed by k because we
-	// need to find all memo entries with certain values of k.
-	//
-	// TODO: The n1 and twoU values in the ukeys follow strict
-	// patterns. For each K value, the n1 values are every integer
-	// between two bounds. For each (K, n1) value, the twoU values
-	// are every integer multiple of a certain base between two
-	// bounds. It might be worth turning these into directly
-	// indexible slices.
-	A := make([]map[ukey]float64, K+1)
-	A[K] = map[ukey]float64{{n1: n1, twoU: twoU}: 0}
-
-	// Compute memo table (k, n1, twoU) triples from high K values
-	// to low K values. This drives the recurrence relation
-	// downward to figure out all of the needed argument triples.
-	//
-	// TODO: Is it possible to generate this table bottom-up? If
-	// so, this could be a pure dynamic programming algorithm and
-	// we could discard the K dimension. We could at least store
-	// the inputs in a more compact representation that replaces
-	// the twoU dimension with an interval and a step size (as
-	// suggested by Cheung, Klotz, not that they make it at all
-	// clear *why* they're suggesting this).
-	tsum := sumint(t) // always ∑ t[0:k]
-	for k := K - 1; k >= 2; k-- {
-		tsum -= t[k]
-		A[k] = make(map[ukey]float64)
-
-		// Construct A[k] from A[k+1].
-		for A_kplus1 := range A[k+1] {
-			rkLow := maxint(0, A_kplus1.n1-tsum)
-			rkHigh := minint(A_kplus1.n1, t[k])
-			for rk := rkLow; rk <= rkHigh; rk++ {
-				twoU_k := A_kplus1.twoU - rk*(a[k+1]-2*A_kplus1.n1+rk)
-				n1_k := A_kplus1.n1 - rk
-				if twoUmin(n1_k, t[:k], a) <= twoU_k && twoU_k <= twoUmax(n1_k, t[:k], a) {
-					key := ukey{n1: n1_k, twoU: twoU_k}
-					A[k][key] = 0
-				}
-			}
-		}
-	}
-
-	// Fill counts in memo table from low K values to high K
-	// values. This unwinds the recurrence relation.
-
-	// Start with K==2 base case.
-	//
-	// TODO: Later computations depend on these, but these don't
-	// depend on anything (including each other), so if K==2, we
-	// can skip the memo table altogether.
-	if K < 2 {
-		panic("K < 2")
-	}
-	N_2 := t[0] + t[1]
-	for A_2i := range A[2] {
-		Asum := 0.0
-		r2Low := maxint(0, A_2i.n1-t[0])
-		r2High := (A_2i.twoU - A_2i.n1*(t[0]-A_2i.n1)) / N_2
-		for r2 := r2Low; r2 <= r2High; r2++ {
-			Asum += mathChoose(t[0], A_2i.n1-r2) *
-				mathChoose(t[1], r2)
-		}
-		A[2][A_2i] = Asum
-	}
-
-	// Derive counts for the rest of the memo table.
-	tsum = t[0] // always ∑ t[0:k-1]
-	for k := 3; k <= K; k++ {
-		tsum += t[k-2]
-
-		// Compute A[k] counts from A[k-1] counts.
-		for A_ki := range A[k] {
-			Asum := 0.0
-			rkLow := maxint(0, A_ki.n1-tsum)
-			rkHigh := minint(A_ki.n1, t[k-1])
-			for rk := rkLow; rk <= rkHigh; rk++ {
-				twoU_kminus1 := A_ki.twoU - rk*(a[k]-2*A_ki.n1+rk)
-				n1_kminus1 := A_ki.n1 - rk
-				x, ok := A[k-1][ukey{n1: n1_kminus1, twoU: twoU_kminus1}]
-				if !ok && twoUmax(n1_kminus1, t[:k-1], a) < twoU_kminus1 {
-					x = mathChoose(tsum, n1_kminus1)
-				}
-				Asum += x * mathChoose(t[k-1], rk)
-			}
-			A[k][A_ki] = Asum
-		}
-	}
-
-	return A
-}
-
-func twoUmin(n1 int, t, a []int) int {
-	K := len(t)
-	twoU := -n1 * n1
-	n1_k := n1
-	for k := 1; k <= K; k++ {
-		twoU_k := minint(n1_k, t[k-1])
-		twoU += twoU_k * a[k]
-		n1_k -= twoU_k
-	}
-	return twoU
-}
-
-func twoUmax(n1 int, t, a []int) int {
-	K := len(t)
-	twoU := -n1 * n1
-	n1_k := n1
-	for k := K; k > 0; k-- {
-		twoU_k := minint(n1_k, t[k-1])
-		twoU += twoU_k * a[k]
-		n1_k -= twoU_k
-	}
-	return twoU
-}
-
-func (d UDist) PMF(U float64) float64 {
-	if U < 0 || U >= 0.5+float64(d.N1*d.N2) {
-		return 0
-	}
-
-	if d.hasTies() {
-		// makeUmemo computes the CDF directly. Take its
-		// difference to get the PMF.
-		p1, ok1 := makeUmemo(int(2*U)-1, d.N1, d.T)[len(d.T)][ukey{d.N1, int(2*U) - 1}]
-		p2, ok2 := makeUmemo(int(2*U), d.N1, d.T)[len(d.T)][ukey{d.N1, int(2 * U)}]
-		if !ok1 || !ok2 {
-			panic("makeUmemo did not return expected memoization table")
-		}
-		return (p2 - p1) / mathChoose(d.N1+d.N2, d.N1)
-	}
-
-	// There are no ties. Use the fast algorithm. U must be integral.
-	Ui := int(math.Floor(U))
-	// TODO: Use symmetry to minimize U
-	return d.p(Ui)[Ui]
-}
-
-func (d UDist) CDF(U float64) float64 {
-	if U < 0 {
-		return 0
-	} else if U >= float64(d.N1*d.N2) {
-		return 1
-	}
-
-	if d.hasTies() {
-		// TODO: Minimize U?
-		p, ok := makeUmemo(int(2*U), d.N1, d.T)[len(d.T)][ukey{d.N1, int(2 * U)}]
-		if !ok {
-			panic("makeUmemo did not return expected memoization table")
-		}
-		return p / mathChoose(d.N1+d.N2, d.N1)
-	}
-
-	// There are no ties. Use the fast algorithm. U must be integral.
-	Ui := int(math.Floor(U))
-	// The distribution is symmetric around U = m * n / 2. Sum up
-	// whichever tail is smaller.
-	flip := Ui >= (d.N1*d.N2+1)/2
-	if flip {
-		Ui = d.N1*d.N2 - Ui - 1
-	}
-	pdfs := d.p(Ui)
-	p := 0.0
-	for _, pdf := range pdfs[:Ui+1] {
-		p += pdf
-	}
-	if flip {
-		p = 1 - p
-	}
-	return p
-}
-
-func (d UDist) Step() float64 {
-	return 0.5
-}
-
-func (d UDist) Bounds() (float64, float64) {
-	// TODO: More precise bounds when there are ties.
-	return 0, float64(d.N1 * d.N2)
-}
diff --git a/perf/go/stats/udist_test.go b/perf/go/stats/udist_test.go
deleted file mode 100644
index baceb32..0000000
--- a/perf/go/stats/udist_test.go
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"fmt"
-	"math"
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func aeqTable(a, b [][]float64) bool {
-	if len(a) != len(b) {
-		return false
-	}
-	for i := range a {
-		if len(a[i]) != len(b[i]) {
-			return false
-		}
-		for j := range a[i] {
-			// "%f" precision
-			if math.Abs(a[i][j]-b[i][j]) >= 0.000001 {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-// U distribution for N=3 up to U=5.
-var udist3 = [][]float64{
-	//    m=1         2         3
-	{0.250000, 0.100000, 0.050000}, // U=0
-	{0.500000, 0.200000, 0.100000}, // U=1
-	{0.750000, 0.400000, 0.200000}, // U=2
-	{1.000000, 0.600000, 0.350000}, // U=3
-	{1.000000, 0.800000, 0.500000}, // U=4
-	{1.000000, 0.900000, 0.650000}, // U=5
-}
-
-// U distribution for N=5 up to U=5.
-var udist5 = [][]float64{
-	//    m=1         2         3         4         5
-	{0.166667, 0.047619, 0.017857, 0.007937, 0.003968}, // U=0
-	{0.333333, 0.095238, 0.035714, 0.015873, 0.007937}, // U=1
-	{0.500000, 0.190476, 0.071429, 0.031746, 0.015873}, // U=2
-	{0.666667, 0.285714, 0.125000, 0.055556, 0.027778}, // U=3
-	{0.833333, 0.428571, 0.196429, 0.095238, 0.047619}, // U=4
-	{1.000000, 0.571429, 0.285714, 0.142857, 0.075397}, // U=5
-}
-
-func TestUDist(t *testing.T) {
-	unittest.SmallTest(t)
-	makeTable := func(n int) [][]float64 {
-		out := make([][]float64, 6)
-		for U := 0; U < 6; U++ {
-			out[U] = make([]float64, n)
-			for m := 1; m <= n; m++ {
-				out[U][m-1] = UDist{N1: m, N2: n}.CDF(float64(U))
-			}
-		}
-		return out
-	}
-	fmtTable := func(a [][]float64) string {
-		out := fmt.Sprintf("%8s", "m=")
-		for m := 1; m <= len(a[0]); m++ {
-			out += fmt.Sprintf("%9d", m)
-		}
-		out += "\n"
-
-		for U, row := range a {
-			out += fmt.Sprintf("U=%-6d", U)
-			for m := 1; m <= len(a[0]); m++ {
-				out += fmt.Sprintf(" %f", row[m-1])
-			}
-			out += "\n"
-		}
-		return out
-	}
-
-	// Compare against tables given in Mann, Whitney (1947).
-	got3 := makeTable(3)
-	if !aeqTable(got3, udist3) {
-		t.Errorf("For n=3, want:\n%sgot:\n%s", fmtTable(udist3), fmtTable(got3))
-	}
-
-	got5 := makeTable(5)
-	if !aeqTable(got5, udist5) {
-		t.Errorf("For n=5, want:\n%sgot:\n%s", fmtTable(udist5), fmtTable(got5))
-	}
-}
-
-func BenchmarkUDist(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		// R uses the exact distribution up to N=50.
-		// N*M/2=1250 is the hardest point to get the CDF for.
-		UDist{N1: 50, N2: 50}.CDF(1250)
-	}
-}
-
-func TestUDistTies(t *testing.T) {
-	unittest.SmallTest(t)
-	makeTable := func(m, N int, t []int, minx, maxx float64) [][]float64 {
-		out := [][]float64{}
-		dist := UDist{N1: m, N2: N - m, T: t}
-		for x := minx; x <= maxx; x += 0.5 {
-			// Convert x from uQt' to uQv'.
-			U := x - float64(m*m)/2
-			P := dist.CDF(U)
-			if len(out) == 0 || !aeq(out[len(out)-1][1], P) {
-				out = append(out, []float64{x, P})
-			}
-		}
-		return out
-	}
-	fmtTable := func(table [][]float64) string {
-		out := ""
-		for _, row := range table {
-			out += fmt.Sprintf("%5.1f %f\n", row[0], row[1])
-		}
-		return out
-	}
-
-	// Compare against Table 1 from Klotz (1966).
-	got := makeTable(5, 10, []int{1, 1, 2, 1, 1, 2, 1, 1}, 12.5, 19.5)
-	want := [][]float64{
-		{12.5, 0.003968}, {13.5, 0.007937},
-		{15.0, 0.023810}, {16.5, 0.047619},
-		{17.5, 0.071429}, {18.0, 0.087302},
-		{19.0, 0.134921}, {19.5, 0.138889},
-	}
-	if !aeqTable(got, want) {
-		t.Errorf("Want:\n%sgot:\n%s", fmtTable(want), fmtTable(got))
-	}
-
-	got = makeTable(10, 21, []int{6, 5, 4, 3, 2, 1}, 52, 87)
-	want = [][]float64{
-		{52.0, 0.000014}, {56.5, 0.000128},
-		{57.5, 0.000145}, {60.0, 0.000230},
-		{61.0, 0.000400}, {62.0, 0.000740},
-		{62.5, 0.000797}, {64.0, 0.000825},
-		{64.5, 0.001165}, {65.5, 0.001477},
-		{66.5, 0.002498}, {67.0, 0.002725},
-		{67.5, 0.002895}, {68.0, 0.003150},
-		{68.5, 0.003263}, {69.0, 0.003518},
-		{69.5, 0.003603}, {70.0, 0.005648},
-		{70.5, 0.005818}, {71.0, 0.006626},
-		{71.5, 0.006796}, {72.0, 0.008157},
-		{72.5, 0.009688}, {73.0, 0.009801},
-		{73.5, 0.010430}, {74.0, 0.011111},
-		{74.5, 0.014230}, {75.0, 0.014612},
-		{75.5, 0.017249}, {76.0, 0.018307},
-		{76.5, 0.020178}, {77.0, 0.022270},
-		{77.5, 0.023189}, {78.0, 0.026931},
-		{78.5, 0.028207}, {79.0, 0.029979},
-		{79.5, 0.030931}, {80.0, 0.038969},
-		{80.5, 0.043063}, {81.0, 0.044262},
-		{81.5, 0.046389}, {82.0, 0.049581},
-		{82.5, 0.056300}, {83.0, 0.058027},
-		{83.5, 0.063669}, {84.0, 0.067454},
-		{84.5, 0.074122}, {85.0, 0.077425},
-		{85.5, 0.083498}, {86.0, 0.094079},
-		{86.5, 0.096693}, {87.0, 0.101132},
-	}
-	if !aeqTable(got, want) {
-		t.Errorf("Want:\n%sgot:\n%s", fmtTable(want), fmtTable(got))
-	}
-
-	got = makeTable(8, 16, []int{2, 2, 2, 2, 2, 2, 2, 2}, 32, 54)
-	want = [][]float64{
-		{32.0, 0.000078}, {34.0, 0.000389},
-		{36.0, 0.001088}, {38.0, 0.002642},
-		{40.0, 0.005905}, {42.0, 0.011500},
-		{44.0, 0.021057}, {46.0, 0.035664},
-		{48.0, 0.057187}, {50.0, 0.086713},
-		{52.0, 0.126263}, {54.0, 0.175369},
-	}
-	if !aeqTable(got, want) {
-		t.Errorf("Want:\n%sgot:\n%s", fmtTable(want), fmtTable(got))
-	}
-
-	// Check remaining tables from Klotz against the reference
-	// implementation.
-	checkRef := func(n1 int, tie []int) {
-		wantPMF1, wantCDF1 := udistRef(n1, tie)
-
-		dist := UDist{N1: n1, N2: sumint(tie) - n1, T: tie}
-		gotPMF, wantPMF := [][]float64{}, [][]float64{}
-		gotCDF, wantCDF := [][]float64{}, [][]float64{}
-		N := sumint(tie)
-		for U := 0.0; U <= float64(n1*(N-n1)); U += 0.5 {
-			gotPMF = append(gotPMF, []float64{U, dist.PMF(U)})
-			gotCDF = append(gotCDF, []float64{U, dist.CDF(U)})
-			wantPMF = append(wantPMF, []float64{U, wantPMF1[int(U*2)]})
-			wantCDF = append(wantCDF, []float64{U, wantCDF1[int(U*2)]})
-		}
-		if !aeqTable(wantPMF, gotPMF) {
-			t.Errorf("For PMF of n1=%v, t=%v, want:\n%sgot:\n%s", n1, tie, fmtTable(wantPMF), fmtTable(gotPMF))
-		}
-		if !aeqTable(wantCDF, gotCDF) {
-			t.Errorf("For CDF of n1=%v, t=%v, want:\n%sgot:\n%s", n1, tie, fmtTable(wantCDF), fmtTable(gotCDF))
-		}
-	}
-	checkRef(5, []int{1, 1, 2, 1, 1, 2, 1, 1})
-	checkRef(5, []int{1, 1, 2, 1, 1, 1, 2, 1})
-	checkRef(5, []int{1, 3, 1, 2, 1, 1, 1})
-	checkRef(8, []int{1, 2, 1, 1, 1, 1, 2, 2, 1, 2})
-	checkRef(12, []int{3, 3, 4, 3, 4, 5})
-	checkRef(10, []int{1, 2, 3, 4, 5, 6})
-}
-
-func BenchmarkUDistTies(b *testing.B) {
-	// Worst case: just one tie.
-	n := 20
-	t := make([]int, 2*n-1)
-	for i := range t {
-		t[i] = 1
-	}
-	t[0] = 2
-
-	for i := 0; i < b.N; i++ {
-		UDist{N1: n, N2: n, T: t}.CDF(float64(n*n) / 2)
-	}
-}
-
-func XTestPrintUmemo(t *testing.T) {
-	// Reproduce table from Cheung, Klotz.
-	ties := []int{4, 5, 3, 4, 6}
-	printUmemo(makeUmemo(80, 10, ties), ties)
-}
-
-// udistRef computes the PMF and CDF of the U distribution for two
-// samples of sizes n1 and sum(t)-n1 with tie vector t. The returned
-// pmf and cdf are indexed by 2*U.
-//
-// This uses the "graphical method" of Klotz (1966). It is very slow
-// (Θ(∏ (t[i]+1)) = Ω(2^|t|)), but very correct, and hence useful as a
-// reference for testing faster implementations.
-func udistRef(n1 int, t []int) (pmf, cdf []float64) {
-	// Enumerate all u vectors for which 0 <= u_i <= t_i. Count
-	// the number of permutations of two samples of sizes n1 and
-	// sum(t)-n1 with tie vector t and accumulate these counts by
-	// their U statistics in count[2*U].
-	counts := make([]int, 1+2*n1*(sumint(t)-n1))
-
-	u := make([]int, len(t))
-	u[0] = -1 // Get enumeration started.
-enumu:
-	for {
-		// Compute the next u vector.
-		u[0]++
-		for i := 0; i < len(u) && u[i] > t[i]; i++ {
-			if i == len(u)-1 {
-				// All u vectors have been enumerated.
-				break enumu
-			}
-			// Carry.
-			u[i+1]++
-			u[i] = 0
-		}
-
-		// Is this a legal u vector?
-		if sumint(u) != n1 {
-			// Klotz (1966) has a method for directly
-			// enumerating legal u vectors, but the point
-			// of this is to be correct, not fast.
-			continue
-		}
-
-		// Compute 2*U statistic for this u vector.
-		twoU, vsum := 0, 0
-		for i, u_i := range u {
-			v_i := t[i] - u_i
-			// U = U + vsum*u_i + u_i*v_i/2
-			twoU += 2*vsum*u_i + u_i*v_i
-			vsum += v_i
-		}
-
-		// Compute Π choose(t_i, u_i). This is the number of
-		// ways of permuting the input sample under u.
-		prod := 1
-		for i, u_i := range u {
-			prod *= int(mathChoose(t[i], u_i) + 0.5)
-		}
-
-		// Accumulate the permutations on this u path.
-		counts[twoU] += prod
-
-		if false {
-			// Print a table in the form of Klotz's
-			// "direct enumeration" example.
-			//
-			// Convert 2U = 2UQV' to UQt' used in Klotz
-			// examples.
-			UQt := float64(twoU)/2 + float64(n1*n1)/2
-			fmt.Printf("%+v %f %-2d\n", u, UQt, prod)
-		}
-	}
-
-	// Convert counts into probabilities for PMF and CDF.
-	pmf = make([]float64, len(counts))
-	cdf = make([]float64, len(counts))
-	total := int(mathChoose(sumint(t), n1) + 0.5)
-	for i, count := range counts {
-		pmf[i] = float64(count) / float64(total)
-		if i > 0 {
-			cdf[i] = cdf[i-1]
-		}
-		cdf[i] += pmf[i]
-	}
-	return
-}
-
-// printUmemo prints the output of makeUmemo for debugging.
-func printUmemo(A []map[ukey]float64, t []int) {
-	fmt.Printf("K\tn1\t2*U\tpr\n")
-	for K := len(A) - 1; K >= 0; K-- {
-		for i, pr := range A[K] {
-			_, ref := udistRef(i.n1, t[:K])
-			fmt.Printf("%v\t%v\t%v\t%v\t%v\n", K, i.n1, i.twoU, pr, ref[i.twoU])
-		}
-	}
-}
diff --git a/perf/go/stats/utest.go b/perf/go/stats/utest.go
deleted file mode 100644
index 048ebe7..0000000
--- a/perf/go/stats/utest.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"math"
-	"sort"
-)
-
-// 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
-
-//go:generate stringer -type LocationHypothesis
-
-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 MannWhitneyUTestResult is the result of a Mann-Whitney U-test.
-type MannWhitneyUTestResult struct {
-	// N1 and N2 are the sizes of the input samples.
-	N1, N2 int
-
-	// U is the value of the Mann-Whitney U statistic for this
-	// test, generalized by counting ties as 0.5.
-	//
-	// Given the Cartesian product of the two samples, this is the
-	// number of pairs in which the value from the first sample is
-	// greater than the value of the second, plus 0.5 times the
-	// number of pairs where the values from the two samples are
-	// equal. Hence, U is always an integer multiple of 0.5 (it is
-	// a whole integer if there are no ties) in the range [0, N1*N2].
-	//
-	// U statistics always come in pairs, depending on which
-	// sample is "first". The mirror U for the other sample can be
-	// calculated as N1*N2 - U.
-	//
-	// There are many equivalent statistics with slightly
-	// different definitions. The Wilcoxon (1945) W statistic
-	// (generalized for ties) is U + (N1(N1+1))/2. It is also
-	// common to use 2U to eliminate the half steps and Smid
-	// (1956) uses N1*N2 - 2U to additionally center the
-	// distribution.
-	U float64
-
-	// AltHypothesis specifies the alternative hypothesis tested
-	// by this test against the null hypothesis that there is no
-	// difference in the locations of the samples.
-	AltHypothesis LocationHypothesis
-
-	// P is the p-value of the Mann-Whitney test for the given
-	// null hypothesis.
-	P float64
-}
-
-// MannWhitneyExactLimit gives the largest sample size for which the
-// exact U distribution will be used for the Mann-Whitney U-test.
-//
-// Using the exact distribution is necessary for small sample sizes
-// because the distribution is highly irregular. However, computing
-// the distribution for large sample sizes is both computationally
-// expensive and unnecessary because it quickly approaches a normal
-// approximation. Computing the distribution for two 50 value samples
-// takes a few milliseconds on a 2014 laptop.
-var MannWhitneyExactLimit = 50
-
-// MannWhitneyTiesExactLimit gives the largest sample size for which
-// the exact U distribution will be used for the Mann-Whitney U-test
-// in the presence of ties.
-//
-// Computing this distribution is more expensive than computing the
-// distribution without ties, so this is set lower. Computing this
-// distribution for two 25 value samples takes about ten milliseconds
-// on a 2014 laptop.
-var MannWhitneyTiesExactLimit = 25
-
-// MannWhitneyUTest performs a Mann-Whitney U-test [1,2] of the null
-// hypothesis that two samples come from the same population against
-// the alternative hypothesis that one sample tends to have larger or
-// smaller values than the other.
-//
-// This is similar to a t-test, but unlike the t-test, the
-// Mann-Whitney U-test is non-parametric (it does not assume a normal
-// distribution). It has very slightly lower efficiency than the
-// t-test on normal distributions.
-//
-// Computing the exact U distribution is expensive for large sample
-// sizes, so this uses a normal approximation for sample sizes larger
-// than MannWhitneyExactLimit if there are no ties or
-// MannWhitneyTiesExactLimit if there are ties. This normal
-// approximation uses both the tie correction and the continuity
-// correction.
-//
-// This can fail with ErrSampleSize if either sample is empty or
-// ErrSamplesEqual if all sample values are equal.
-//
-// This is also known as a Mann-Whitney-Wilcoxon test and is
-// equivalent to the Wilcoxon rank-sum test, though the Wilcoxon
-// rank-sum test differs in nomenclature.
-//
-// [1] Mann, Henry B.; Whitney, Donald R. (1947). "On a Test of
-// Whether one of Two Random Variables is Stochastically Larger than
-// the Other". Annals of Mathematical Statistics 18 (1): 50–60.
-//
-// [2] Klotz, J. H. (1966). "The Wilcoxon, Ties, and the Computer".
-// Journal of the American Statistical Association 61 (315): 772-787.
-func MannWhitneyUTest(x1, x2 []float64, alt LocationHypothesis) (*MannWhitneyUTestResult, error) {
-	n1, n2 := len(x1), len(x2)
-	if n1 == 0 || n2 == 0 {
-		return nil, ErrSampleSize
-	}
-
-	// Compute the U statistic and tie vector T.
-	x1 = append([]float64(nil), x1...)
-	x2 = append([]float64(nil), x2...)
-	sort.Float64s(x1)
-	sort.Float64s(x2)
-	merged, labels := labeledMerge(x1, x2)
-
-	R1 := 0.0
-	T, hasTies := []int{}, false
-	for i := 0; i < len(merged); {
-		rank1, nx1, v1 := i+1, 0, merged[i]
-		// Consume samples that tie this sample (including itself).
-		for ; i < len(merged) && merged[i] == v1; i++ {
-			if labels[i] == 1 {
-				nx1++
-			}
-		}
-		// Assign all tied samples the average rank of the
-		// samples, where merged[0] has rank 1.
-		if nx1 != 0 {
-			rank := float64(i+rank1) / 2
-			R1 += rank * float64(nx1)
-		}
-		T = append(T, i-rank1+1)
-		if i > rank1 {
-			hasTies = true
-		}
-	}
-	U1 := R1 - float64(n1*(n1+1))/2
-
-	// Compute the smaller of U1 and U2
-	U2 := float64(n1*n2) - U1
-	Usmall := math.Min(U1, U2)
-
-	var p float64
-	if !hasTies && n1 <= MannWhitneyExactLimit && n2 <= MannWhitneyExactLimit ||
-		hasTies && n1 <= MannWhitneyTiesExactLimit && n2 <= MannWhitneyTiesExactLimit {
-		// Use exact U distribution. U1 will be an integer.
-		if len(T) == 1 {
-			// All values are equal. Test is meaningless.
-			return nil, ErrSamplesEqual
-		}
-
-		dist := UDist{N1: n1, N2: n2, T: T}
-		switch alt {
-		case LocationDiffers:
-			if U1 == U2 {
-				// The distribution is symmetric about
-				// Usmall. Since the distribution is
-				// discrete, the CDF is discontinuous
-				// and if simply double CDF(Usmall),
-				// we'll double count the
-				// (non-infinitesimal) probability
-				// mass at Usmall. What we want is
-				// just the integral of the whole CDF,
-				// which is 1.
-				p = 1
-			} else {
-				p = dist.CDF(Usmall) * 2
-			}
-
-		case LocationLess:
-			p = dist.CDF(U1)
-
-		case LocationGreater:
-			p = 1 - dist.CDF(U1-1)
-		}
-	} else {
-		// Use normal approximation (with tie and continuity
-		// correction).
-		t := tieCorrection(T)
-		N := float64(n1 + n2)
-		μ_U := float64(n1*n2) / 2
-		σ_U := math.Sqrt(float64(n1*n2) * ((N + 1) - t/(N*(N-1))) / 12)
-		if σ_U == 0 {
-			return nil, ErrSamplesEqual
-		}
-		numer := U1 - μ_U
-		// Perform continuity correction.
-		switch alt {
-		case LocationDiffers:
-			numer -= mathSign(numer) * 0.5
-		case LocationLess:
-			numer += 0.5
-		case LocationGreater:
-			numer -= 0.5
-		}
-		z := numer / σ_U
-		switch alt {
-		case LocationDiffers:
-			p = 2 * math.Min(StdNormal.CDF(z), 1-StdNormal.CDF(z))
-		case LocationLess:
-			p = StdNormal.CDF(z)
-		case LocationGreater:
-			p = 1 - StdNormal.CDF(z)
-		}
-	}
-
-	return &MannWhitneyUTestResult{N1: n1, N2: n2, U: U1,
-		AltHypothesis: alt, P: p}, nil
-}
-
-// labeledMerge merges sorted lists x1 and x2 into sorted list merged.
-// labels[i] is 1 or 2 depending on whether merged[i] is a value from
-// x1 or x2, respectively.
-func labeledMerge(x1, x2 []float64) (merged []float64, labels []byte) {
-	merged = make([]float64, len(x1)+len(x2))
-	labels = make([]byte, len(x1)+len(x2))
-
-	i, j, o := 0, 0, 0
-	for i < len(x1) && j < len(x2) {
-		if x1[i] < x2[j] {
-			merged[o] = x1[i]
-			labels[o] = 1
-			i++
-		} else {
-			merged[o] = x2[j]
-			labels[o] = 2
-			j++
-		}
-		o++
-	}
-	for ; i < len(x1); i++ {
-		merged[o] = x1[i]
-		labels[o] = 1
-		o++
-	}
-	for ; j < len(x2); j++ {
-		merged[o] = x2[j]
-		labels[o] = 2
-		o++
-	}
-	return
-}
-
-// tieCorrection computes the tie correction factor Σ_j (t_j³ - t_j)
-// where t_j is the number of ties in the j'th rank.
-func tieCorrection(ties []int) float64 {
-	t := 0
-	for _, tie := range ties {
-		t += tie*tie*tie - tie
-	}
-	return float64(t)
-}
diff --git a/perf/go/stats/utest_test.go b/perf/go/stats/utest_test.go
deleted file mode 100644
index a3e3f19..0000000
--- a/perf/go/stats/utest_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"testing"
-
-	"go.skia.org/infra/go/testutils/unittest"
-)
-
-func TestMannWhitneyUTest(t *testing.T) {
-	unittest.SmallTest(t)
-	check := func(want, got *MannWhitneyUTestResult) {
-		if want.N1 != got.N1 || want.N2 != got.N2 ||
-			!aeq(want.U, got.U) ||
-			want.AltHypothesis != got.AltHypothesis ||
-			!aeq(want.P, got.P) {
-			t.Errorf("want %+v, got %+v", want, got)
-		}
-	}
-	check3 := func(x1, x2 []float64, U float64, pless, pdiff, pgreater float64) {
-		want := &MannWhitneyUTestResult{N1: len(x1), N2: len(x2), U: U}
-
-		want.AltHypothesis = LocationLess
-		want.P = pless
-		got, _ := MannWhitneyUTest(x1, x2, want.AltHypothesis)
-		check(want, got)
-
-		want.AltHypothesis = LocationDiffers
-		want.P = pdiff
-		got, _ = MannWhitneyUTest(x1, x2, want.AltHypothesis)
-		check(want, got)
-
-		want.AltHypothesis = LocationGreater
-		want.P = pgreater
-		got, _ = MannWhitneyUTest(x1, x2, want.AltHypothesis)
-		check(want, got)
-	}
-
-	s1 := []float64{2, 1, 3, 5}
-	s2 := []float64{12, 11, 13, 15}
-	s3 := []float64{0, 4, 6, 7} // Interleaved with s1, but no ties
-	s4 := []float64{2, 2, 2, 2}
-	s5 := []float64{1, 1, 1, 1, 1}
-
-	// Small sample, no ties
-	check3(s1, s2, 0, 0.014285714285714289, 0.028571428571428577, 1)
-	check3(s2, s1, 16, 1, 0.028571428571428577, 0.014285714285714289)
-	check3(s1, s3, 5, 0.24285714285714288, 0.485714285714285770, 0.8285714285714285)
-
-	// Small sample, ties
-	// TODO: Check these against some other implementation.
-	check3(s1, s1, 8, 0.6285714285714286, 1, 0.6285714285714286)
-	check3(s1, s4, 10, 0.8571428571428571, 0.7142857142857143, 0.3571428571428571)
-	check3(s1, s5, 17.5, 1, 0, 0.04761904761904767)
-
-	r, err := MannWhitneyUTest(s4, s4, LocationDiffers)
-	if err != ErrSamplesEqual {
-		t.Errorf("want ErrSamplesEqual, got %+v, %+v", r, err)
-	}
-
-	// Large samples.
-	l1 := make([]float64, 500)
-	for i := range l1 {
-		l1[i] = float64(i * 2)
-	}
-	l2 := make([]float64, 600)
-	for i := range l2 {
-		l2[i] = float64(i*2 - 41)
-	}
-	l3 := append([]float64{}, l2...)
-	for i := 0; i < 30; i++ {
-		l3[i] = l1[i]
-	}
-	// For comparing with R's wilcox.test:
-	// l1 <- seq(0, 499)*2
-	// l2 <- seq(0,599)*2-41
-	// l3 <- l2; for (i in 1:30) { l3[i] = l1[i] }
-
-	check3(l1, l2, 135250, 0.0024667680407086112, 0.0049335360814172224, 0.9975346930458906)
-	check3(l1, l1, 125000, 0.5000436801680628, 1, 0.5000436801680628)
-	check3(l1, l3, 134845, 0.0019351907119808942, 0.0038703814239617884, 0.9980659818257166)
-}
diff --git a/perf/go/stats/util_test.go b/perf/go/stats/util_test.go
deleted file mode 100644
index 4ab13f6..0000000
--- a/perf/go/stats/util_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2015 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 stats
-
-import (
-	"fmt"
-	"math"
-	"sort"
-	"strings"
-	"testing"
-)
-
-func testDiscreteCDF(t *testing.T, name string, dist DiscreteDist) {
-	// Build the expected CDF out of the PMF.
-	l, h := dist.Bounds()
-	s := dist.Step()
-	want := map[float64]float64{l - 0.1: 0, h: 1}
-	sum := 0.0
-	for x := l; x < h; x += s {
-		sum += dist.PMF(x)
-		want[x] = sum
-		want[x+s/2] = sum
-	}
-
-	testFunc(t, name, dist.CDF, want)
-}
-
-func testInvCDF(t *testing.T, dist Dist, bounded bool) {
-	inv := InvCDF(dist)
-	name := fmt.Sprintf("InvCDF(%+v)", dist)
-	cdfName := fmt.Sprintf("CDF(%+v)", dist)
-
-	// Test bounds.
-	vals := map[float64]float64{-0.01: nan, 1.01: nan}
-	if !bounded {
-		vals[0] = -inf
-		vals[1] = inf
-	}
-	testFunc(t, name, inv, vals)
-
-	if bounded {
-		lo, hi := inv(0), inv(1)
-		vals := map[float64]float64{
-			lo - 0.01: 0, lo: 0,
-			hi: 1, hi + 0.01: 1,
-		}
-		testFunc(t, cdfName, dist.CDF, vals)
-		if got := dist.CDF(lo + 0.01); !(got > 0) {
-			t.Errorf("%s(0)=%v, but %s(%v)=0", name, lo, cdfName, lo+0.01)
-		}
-		if got := dist.CDF(hi - 0.01); !(got < 1) {
-			t.Errorf("%s(1)=%v, but %s(%v)=1", name, hi, cdfName, hi-0.01)
-		}
-	}
-
-	// Test points between.
-	vals = map[float64]float64{}
-	for _, p := range vecLinspace(0, 1, 11) {
-		if p == 0 || p == 1 {
-			continue
-		}
-		x := inv(p)
-		vals[x] = x
-	}
-	testFunc(t, fmt.Sprintf("InvCDF(CDF(%+v))", dist),
-		func(x float64) float64 {
-			return inv(dist.CDF(x))
-		},
-		vals)
-}
-
-// aeq returns true if expect and got are equal to 8 significant
-// figures (1 part in 100 million).
-func aeq(expect, got float64) bool {
-	if expect < 0 && got < 0 {
-		expect, got = -expect, -got
-	}
-	return expect*0.99999999 <= got && got*0.99999999 <= expect
-}
-
-func testFunc(t *testing.T, name string, f func(float64) float64, vals map[float64]float64) {
-	xs := make([]float64, 0, len(vals))
-	for x := range vals {
-		xs = append(xs, x)
-	}
-	sort.Float64s(xs)
-
-	for _, x := range xs {
-		want, got := vals[x], f(x)
-		if math.IsNaN(want) && math.IsNaN(got) || aeq(want, got) {
-			continue
-		}
-		var label string
-		if strings.Contains(name, "%v") {
-			label = fmt.Sprintf(name, x)
-		} else {
-			label = fmt.Sprintf("%s(%v)", name, x)
-		}
-		t.Errorf("want %s=%v, got %v", label, want, got)
-	}
-}
-
-// vecLinspace returns num values spaced evenly between lo and hi,
-// inclusive. If num is 1, this returns an array consisting of lo.
-func vecLinspace(lo, hi float64, num int) []float64 {
-	res := make([]float64, num)
-	if num == 1 {
-		res[0] = lo
-		return res
-	}
-	for i := 0; i < num; i++ {
-		res[i] = lo + float64(i)*(hi-lo)/float64(num-1)
-	}
-	return res
-}