github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/math/big/calibrate_test.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Calibration used to determine thresholds for using
     6  // different algorithms.  Ideally, this would be converted
     7  // to go generate to create thresholds.go
     8  
     9  // This file prints execution times for the Mul benchmark
    10  // given different Karatsuba thresholds. The result may be
    11  // used to manually fine-tune the threshold constant. The
    12  // results are somewhat fragile; use repeated runs to get
    13  // a clear picture.
    14  
    15  // Calculates lower and upper thresholds for when basicSqr
    16  // is faster than standard multiplication.
    17  
    18  // Usage: go test -run=TestCalibrate -v -calibrate
    19  
    20  package big
    21  
    22  import (
    23  	"flag"
    24  	"fmt"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  var calibrate = flag.Bool("calibrate", false, "run calibration test")
    30  
    31  func TestCalibrate(t *testing.T) {
    32  	if *calibrate {
    33  		computeKaratsubaThresholds()
    34  
    35  		// compute basicSqrThreshold where overhead becomes negligible
    36  		minSqr := computeSqrThreshold(10, 30, 1, 3)
    37  		// compute karatsubaSqrThreshold where karatsuba is faster
    38  		maxSqr := computeSqrThreshold(300, 500, 10, 3)
    39  		if minSqr != 0 {
    40  			fmt.Printf("found basicSqrThreshold = %d\n", minSqr)
    41  		} else {
    42  			fmt.Println("no basicSqrThreshold found")
    43  		}
    44  		if maxSqr != 0 {
    45  			fmt.Printf("found karatsubaSqrThreshold = %d\n", maxSqr)
    46  		} else {
    47  			fmt.Println("no karatsubaSqrThreshold found")
    48  		}
    49  	}
    50  }
    51  
    52  func karatsubaLoad(b *testing.B) {
    53  	BenchmarkMul(b)
    54  }
    55  
    56  // measureKaratsuba returns the time to run a Karatsuba-relevant benchmark
    57  // given Karatsuba threshold th.
    58  func measureKaratsuba(th int) time.Duration {
    59  	th, karatsubaThreshold = karatsubaThreshold, th
    60  	res := testing.Benchmark(karatsubaLoad)
    61  	karatsubaThreshold = th
    62  	return time.Duration(res.NsPerOp())
    63  }
    64  
    65  func computeKaratsubaThresholds() {
    66  	fmt.Printf("Multiplication times for varying Karatsuba thresholds\n")
    67  	fmt.Printf("(run repeatedly for good results)\n")
    68  
    69  	// determine Tk, the work load execution time using basic multiplication
    70  	Tb := measureKaratsuba(1e9) // th == 1e9 => Karatsuba multiplication disabled
    71  	fmt.Printf("Tb = %10s\n", Tb)
    72  
    73  	// thresholds
    74  	th := 4
    75  	th1 := -1
    76  	th2 := -1
    77  
    78  	var deltaOld time.Duration
    79  	for count := -1; count != 0 && th < 128; count-- {
    80  		// determine Tk, the work load execution time using Karatsuba multiplication
    81  		Tk := measureKaratsuba(th)
    82  
    83  		// improvement over Tb
    84  		delta := (Tb - Tk) * 100 / Tb
    85  
    86  		fmt.Printf("th = %3d  Tk = %10s  %4d%%", th, Tk, delta)
    87  
    88  		// determine break-even point
    89  		if Tk < Tb && th1 < 0 {
    90  			th1 = th
    91  			fmt.Print("  break-even point")
    92  		}
    93  
    94  		// determine diminishing return
    95  		if 0 < delta && delta < deltaOld && th2 < 0 {
    96  			th2 = th
    97  			fmt.Print("  diminishing return")
    98  		}
    99  		deltaOld = delta
   100  
   101  		fmt.Println()
   102  
   103  		// trigger counter
   104  		if th1 >= 0 && th2 >= 0 && count < 0 {
   105  			count = 10 // this many extra measurements after we got both thresholds
   106  		}
   107  
   108  		th++
   109  	}
   110  }
   111  
   112  func measureBasicSqr(words, nruns int, enable bool) time.Duration {
   113  	// more runs for better statistics
   114  	initBasicSqr, initKaratsubaSqr := basicSqrThreshold, karatsubaSqrThreshold
   115  
   116  	if enable {
   117  		// set thresholds to use basicSqr at this number of words
   118  		basicSqrThreshold, karatsubaSqrThreshold = words-1, words+1
   119  	} else {
   120  		// set thresholds to disable basicSqr for any number of words
   121  		basicSqrThreshold, karatsubaSqrThreshold = -1, -1
   122  	}
   123  
   124  	var testval int64
   125  	for i := 0; i < nruns; i++ {
   126  		res := testing.Benchmark(func(b *testing.B) { benchmarkNatSqr(b, words) })
   127  		testval += res.NsPerOp()
   128  	}
   129  	testval /= int64(nruns)
   130  
   131  	basicSqrThreshold, karatsubaSqrThreshold = initBasicSqr, initKaratsubaSqr
   132  
   133  	return time.Duration(testval)
   134  }
   135  
   136  func computeSqrThreshold(from, to, step, nruns int) int {
   137  	fmt.Println("Calibrating thresholds for basicSqr via benchmarks of z.mul(x,x)")
   138  	fmt.Printf("Looking for a timing difference for x between %d - %d words by %d step\n", from, to, step)
   139  	var initPos bool
   140  	var threshold int
   141  	for i := from; i <= to; i += step {
   142  		baseline := measureBasicSqr(i, nruns, false)
   143  		testval := measureBasicSqr(i, nruns, true)
   144  		pos := baseline > testval
   145  		delta := baseline - testval
   146  		percent := delta * 100 / baseline
   147  		fmt.Printf("words = %3d deltaT = %10s (%4d%%) is basicSqr better: %v", i, delta, percent, pos)
   148  		if i == from {
   149  			initPos = pos
   150  		}
   151  		if threshold == 0 && pos != initPos {
   152  			threshold = i
   153  			fmt.Printf("  threshold  found")
   154  		}
   155  		fmt.Println()
   156  
   157  	}
   158  	if threshold != 0 {
   159  		fmt.Printf("Found threshold = %d between %d - %d\n", threshold, from, to)
   160  	} else {
   161  		fmt.Printf("Found NO threshold between %d - %d\n", from, to)
   162  	}
   163  	return threshold
   164  }