github.com/haraldrudell/parl@v0.4.176/prand/rand_bench_test.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package prand
     7  
     8  import (
     9  	cryptorand "crypto/rand"
    10  	"math/rand"
    11  	"testing"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"github.com/haraldrudell/parl/ptesting"
    16  )
    17  
    18  // How to generate pseudo-random numbers efficiently?
    19  //	- use fastrand 2.092 ns
    20  //	- create thread-local math/rand.Rand and then use Uint32: 2.240 ns
    21  //	- pick a slow option
    22  
    23  // go test -run=^# -bench=BenchmarkRand32 ./ptesting
    24  // 5 s
    25  //
    26  // goversion: go1.20.1
    27  // osversion: macOS 13.2.1
    28  // goos: darwin
    29  // goarch: arm64
    30  // pkg: github.com/haraldrudell/parl/ptesting
    31  // BenchmarkRand32/math/rand.Uint32-10         	84712857	        14.19 ns/op	       0 B/op	       0 allocs/op
    32  // BenchmarkRand32/crypto/rand.Read_32-bit-10  	 3601936	       329.7 ns/op	       0 B/op	       0 allocs/op
    33  // BenchmarkRand32/ptesting.Uint32-10          	566568112	         2.162 ns/op	       0 B/op	       0 allocs/op
    34  func BenchmarkRand32(b *testing.B) {
    35  
    36  	println(ptesting.Versions())
    37  
    38  	// math/rand.Uint32: thread-safe
    39  	b.Run("math/rand.Uint32", func(b *testing.B) {
    40  		for iteration := 0; iteration < b.N; iteration++ {
    41  			rand.Uint32()
    42  		}
    43  	})
    44  
    45  	// math/rand.Uint32: thread-safe
    46  	b.Run("math/rand.Rand.Uint32", func(b *testing.B) {
    47  		// random generator with thread-local seed
    48  		var r = rand.New(rand.NewSource(0))
    49  		for iteration := 0; iteration < b.N; iteration++ {
    50  			r.Uint32()
    51  		}
    52  	})
    53  
    54  	// crypto/rand.Read: thread-safe
    55  	b.Run("crypto/rand.Read 32-bit", func(b *testing.B) {
    56  		var length = unsafe.Sizeof(uint32(1))
    57  		var byts = make([]byte, length)
    58  		b.ResetTimer()
    59  		for iteration := 0; iteration < b.N; iteration++ {
    60  			cryptorand.Read(byts)
    61  		}
    62  	})
    63  
    64  	// runtime.fastrand 32-bit: thread-safe
    65  	b.Run("ptesting.Uint32", func(b *testing.B) {
    66  		for iteration := 0; iteration < b.N; iteration++ {
    67  			Uint32()
    68  		}
    69  	})
    70  }
    71  
    72  // go test -run=^# -bench=BenchmarkRand64 ./ptesting
    73  // 5 s
    74  //
    75  // goversion: go1.20.1
    76  // osversion: macOS 13.2.1
    77  // goos: darwin
    78  // goarch: arm64
    79  // pkg: github.com/haraldrudell/parl/ptesting
    80  // BenchmarkRand64/math/rand.Uint64-10         	84433459	        14.01 ns/op	       0 B/op	       0 allocs/op
    81  // BenchmarkRand64/crypto/rand.Read_64-bit-10  	 3783060	       323.8 ns/op	       0 B/op	       0 allocs/op
    82  // BenchmarkRand64/ptesting.Uint64-10          	277811217	         4.289 ns/op	       0 B/op	       0 allocs/op
    83  func BenchmarkRand64(b *testing.B) {
    84  
    85  	println(ptesting.Versions())
    86  
    87  	// math/rand.Uint64: thread-safe
    88  	b.Run("math/rand.Uint64", func(b *testing.B) {
    89  		for iteration := 0; iteration < b.N; iteration++ {
    90  			rand.Uint64()
    91  		}
    92  	})
    93  
    94  	// crypto/rand.Read: thread-safe
    95  	b.Run("crypto/rand.Read 64-bit", func(b *testing.B) {
    96  		var length = unsafe.Sizeof(uint64(1))
    97  		var byts = make([]byte, length)
    98  		b.ResetTimer()
    99  		for iteration := 0; iteration < b.N; iteration++ {
   100  			cryptorand.Read(byts)
   101  		}
   102  	})
   103  
   104  	// runtime.fastrand 64-bit: thread-safe
   105  	b.Run("ptesting.Uint64", func(b *testing.B) {
   106  		for iteration := 0; iteration < b.N; iteration++ {
   107  			Uint64()
   108  		}
   109  	})
   110  }
   111  
   112  // thread-local math/rand.Rand.Uint32 pseudo-random number on 2021 M1 Max is 2.240 ns
   113  //   - thread must have Rand available
   114  //
   115  // Running tool: /opt/homebrew/bin/go test -benchmem -run=^$ -bench ^BenchmarkNewRand$ github.com/haraldrudell/parl/prand
   116  // goos: darwin
   117  // goarch: arm64
   118  // pkg: github.com/haraldrudell/parl/prand
   119  // BenchmarkNewRand-10    	53342386	        22.40 ns/op	         2.240 wall-ns/op	       0 B/op	       0 allocs/op
   120  // PASS
   121  // ok  	github.com/haraldrudell/parl/prand	2.234s
   122  func BenchmarkNewRand(b *testing.B) {
   123  	const uint32Count = 10
   124  	var r = rand.New(rand.NewSource(time.Now().UnixNano()))
   125  	b.ResetTimer()
   126  	for i := 0; i < b.N; i++ {
   127  		r.Uint32() // 1
   128  		r.Uint32() // 2
   129  		r.Uint32() // 3
   130  		r.Uint32() // 4
   131  		r.Uint32() // 5
   132  		r.Uint32() // 6
   133  		r.Uint32() // 7
   134  		r.Uint32() // 8
   135  		r.Uint32() // 9
   136  		r.Uint32() // 10
   137  	}
   138  	b.StopTimer()
   139  	// elapsed is duration of 10 fastrand invocations. Unit ns, int64
   140  	var elapsed = b.Elapsed()
   141  	// nUnit is wall-time nanoseconds per fastrand invocation
   142  	//	- float64, unit ns
   143  	var nUnit float64 = //
   144  	float64(elapsed) /
   145  		float64(b.N) /
   146  		uint32Count
   147  	b.ReportMetric(nUnit, "wall-ns/op")
   148  }
   149  
   150  // time.Now().UnixNano() as 64-bit pseudo-random number on 2021 M1 Max is 38.00 ns
   151  //
   152  // 231114 c66
   153  // Running tool: /opt/homebrew/bin/go test -benchmem -run=^$ -bench ^BenchmarkTimeNow$ github.com/haraldrudell/parl/prand
   154  // goos: darwin
   155  // goarch: arm64
   156  // pkg: github.com/haraldrudell/parl/prand
   157  // BenchmarkTimeNow-10    	31434660	        38.00 ns/op	       0 B/op	       0 allocs/op
   158  // PASS
   159  // ok  	github.com/haraldrudell/parl/prand	2.325s
   160  func BenchmarkTimeNow(b *testing.B) {
   161  	for i := 0; i < b.N; i++ {
   162  		time.Now().UnixNano()
   163  	}
   164  }
   165  
   166  // fastrand 32-bit pseudo-random number on 2021 M1 Max is 2.092 ns
   167  //
   168  // 231114 c66
   169  // Running tool: /opt/homebrew/bin/go test -benchmem -run=^$ -bench ^BenchmarkFastRand$ github.com/haraldrudell/parl/prand
   170  // goos: darwin
   171  // goarch: arm64
   172  // pkg: github.com/haraldrudell/parl/prand
   173  // BenchmarkFastRand-10    	57573570	        20.92 ns/op	         2.092 wall-ns/op	       0 B/op	       0 allocs/op
   174  // PASS
   175  // ok  	github.com/haraldrudell/parl/prand	2.288s
   176  func BenchmarkFastRand(b *testing.B) {
   177  	const fastRandCount = 10
   178  	for i := 0; i < b.N; i++ {
   179  		// fastrand invocation is 2.039 ns which is too short to be tested by itself
   180  		//	- at least 4 ns of measured task is required for metric quality
   181  		//	- 10 fastrand is 21.10 ns
   182  		fastrand() // 1
   183  		fastrand() // 2
   184  		fastrand() // 3
   185  		fastrand() // 4
   186  		fastrand() // 5
   187  		fastrand() // 6
   188  		fastrand() // 7
   189  		fastrand() // 8
   190  		fastrand() // 9
   191  		fastrand() // 10
   192  	}
   193  	b.StopTimer()
   194  	// elapsed is duration of 10 fastrand invocations. Unit ns, int64
   195  	var elapsed = b.Elapsed()
   196  	// nUnit is wall-time nanoseconds per fastrand invocation
   197  	//	- float64, unit ns
   198  	var nUnit float64 = //
   199  	float64(elapsed) /
   200  		float64(b.N) /
   201  		fastRandCount
   202  	b.ReportMetric(nUnit, "wall-ns/op")
   203  }