-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dec: add calibration tests and update thresholds
- Loading branch information
Showing
3 changed files
with
196 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// Copyright 2009 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. | ||
|
||
// Calibration used to determine thresholds for using | ||
// different algorithms. Ideally, this would be converted | ||
// to go generate to create thresholds.go | ||
|
||
// This file prints execution times for the Mul benchmark | ||
// given different Karatsuba thresholds. The result may be | ||
// used to manually fine-tune the threshold constant. The | ||
// results are somewhat fragile; use repeated runs to get | ||
// a clear picture. | ||
|
||
// Calculates lower and upper thresholds for when basicSqr | ||
// is faster than standard multiplication. | ||
|
||
// Usage: go test -run=TestDecCalibrate -v -calibrate -cpu 1 | ||
// Forcing a single logical CPU seems to yield more stable | ||
// benchmarks. | ||
|
||
package decimal | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"testing" | ||
"time" | ||
) | ||
|
||
var calibrate = flag.Bool("calibrate", false, "run calibration test") | ||
|
||
const ( | ||
sqrModeMul = "mul(x, x)" | ||
sqrModeBasic = "basicSqr(x)" | ||
sqrModeKaratsuba = "karatsubaSqr(x)" | ||
) | ||
|
||
func TestDecCalibrate(t *testing.T) { | ||
if !*calibrate { | ||
return | ||
} | ||
|
||
computeKaratsubaThresholds() | ||
|
||
// compute basicSqrThreshold where overhead becomes negligible | ||
minSqr := computeSqrThreshold(5, 20, 1, 3, sqrModeMul, sqrModeBasic) | ||
// compute karatsubaSqrThreshold where karatsuba is faster | ||
maxSqr := computeSqrThreshold(30, 300, 10, 3, sqrModeBasic, sqrModeKaratsuba) | ||
if minSqr != 0 { | ||
fmt.Printf("found basicSqrThreshold = %d\n", minSqr) | ||
} else { | ||
fmt.Println("no basicSqrThreshold found") | ||
} | ||
if maxSqr != 0 { | ||
fmt.Printf("found karatsubaSqrThreshold = %d\n", maxSqr) | ||
} else { | ||
fmt.Println("no karatsubaSqrThreshold found") | ||
} | ||
} | ||
|
||
func karatsubaLoad(b *testing.B) { | ||
BenchmarkDecMul1e4(b) | ||
} | ||
|
||
// measureKaratsuba returns the time to run a Karatsuba-relevant benchmark | ||
// given Karatsuba threshold th. | ||
func measureKaratsuba(th int) time.Duration { | ||
th, decKaratsubaThreshold = decKaratsubaThreshold, th | ||
res := testing.Benchmark(karatsubaLoad) | ||
decKaratsubaThreshold = th | ||
return time.Duration(res.NsPerOp()) | ||
} | ||
|
||
func computeKaratsubaThresholds() { | ||
fmt.Printf("Multiplication times for varying Karatsuba thresholds\n") | ||
fmt.Printf("(run repeatedly for good results)\n") | ||
|
||
// determine Tk, the work load execution time using basic multiplication | ||
Tb := measureKaratsuba(1e9) // th == 1e9 => Karatsuba multiplication disabled | ||
fmt.Printf("Tb = %10s\n", Tb) | ||
|
||
// thresholds | ||
th := 4 | ||
th1 := -1 | ||
th2 := -1 | ||
|
||
var deltaOld time.Duration | ||
for count := -1; count != 0 && th < 128; count-- { | ||
// determine Tk, the work load execution time using Karatsuba multiplication | ||
Tk := measureKaratsuba(th) | ||
|
||
// improvement over Tb | ||
delta := (Tb - Tk) * 100 / Tb | ||
|
||
fmt.Printf("th = %3d Tk = %10s %4d%%", th, Tk, delta) | ||
|
||
// determine break-even point | ||
if Tk < Tb && th1 < 0 { | ||
th1 = th | ||
fmt.Print(" break-even point") | ||
} | ||
|
||
// determine diminishing return | ||
if 0 < delta && delta < deltaOld && th2 < 0 { | ||
th2 = th | ||
fmt.Print(" diminishing return") | ||
} | ||
deltaOld = delta | ||
|
||
fmt.Println() | ||
|
||
// trigger counter | ||
if th1 >= 0 && th2 >= 0 && count < 0 { | ||
count = 10 // this many extra measurements after we got both thresholds | ||
} | ||
|
||
th++ | ||
} | ||
} | ||
|
||
func measureSqr(words, nruns int, mode string) time.Duration { | ||
// more runs for better statistics | ||
initBasicSqr, initKaratsubaSqr := decBasicSqrThreshold, decKaratsubaSqrThreshold | ||
|
||
switch mode { | ||
case sqrModeMul: | ||
decBasicSqrThreshold = words + 1 | ||
case sqrModeBasic: | ||
decBasicSqrThreshold, decKaratsubaSqrThreshold = words-1, words+1 | ||
case sqrModeKaratsuba: | ||
decKaratsubaSqrThreshold = words - 1 | ||
} | ||
|
||
var testval int64 | ||
for i := 0; i < nruns; i++ { | ||
res := testing.Benchmark(func(b *testing.B) { benchmarkDecSqr(b, words) }) | ||
testval += res.NsPerOp() | ||
} | ||
testval /= int64(nruns) | ||
|
||
decBasicSqrThreshold, decKaratsubaSqrThreshold = initBasicSqr, initKaratsubaSqr | ||
|
||
return time.Duration(testval) | ||
} | ||
|
||
func computeSqrThreshold(from, to, step, nruns int, lower, upper string) int { | ||
fmt.Printf("Calibrating threshold between %s and %s\n", lower, upper) | ||
fmt.Printf("Looking for a timing difference for x between %d - %d words by %d step\n", from, to, step) | ||
var initPos bool | ||
var threshold int | ||
for i := from; i <= to; i += step { | ||
baseline := measureSqr(i, nruns, lower) | ||
testval := measureSqr(i, nruns, upper) | ||
pos := baseline > testval | ||
delta := baseline - testval | ||
percent := delta * 100 / baseline | ||
fmt.Printf("words = %3d deltaT = %10s (%4d%%) is %s better: %v", i, delta, percent, upper, pos) | ||
if i == from { | ||
initPos = pos | ||
} | ||
if threshold == 0 && pos != initPos { | ||
threshold = i | ||
fmt.Printf(" threshold found") | ||
} | ||
fmt.Println() | ||
|
||
} | ||
if threshold != 0 { | ||
fmt.Printf("Found threshold = %d between %d - %d\n", threshold, from, to) | ||
} else { | ||
fmt.Printf("Found NO threshold between %d - %d\n", from, to) | ||
} | ||
return threshold | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters