This commit is contained in:
Jeffrey Paul 2024-05-18 18:37:05 -07:00
commit 81b25d1421
8 changed files with 381 additions and 0 deletions

47
.gitignore vendored Normal file
View File

@ -0,0 +1,47 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Go workspace file
go.work
go.work.sum
# Dependency directories
vendor/
# Go build artifacts
bin/
build/
dist/
# Configuration files for editors and tools
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log
# Temporary files
*.tmp
*.temp
*.bak
# Test binary, coverage, and results
*.test
*.cover
*.cov
*.profile
*.prof
# OS-specific files
.DS_Store
Thumbs.db

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified copies of
this license document, and changing it is allowed as long as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
default: test
test:
go test -v -count=1 ./...

94
README.md Normal file
View File

@ -0,0 +1,94 @@
# TimingBench
`TimingBench` is a Go module for benchmarking the execution time of a
function. It runs the function a specified number of times and returns the
minimum, maximum, mean, and median execution times, along with other
relevant statistics. It also supports context for cancellation.
## Features
- Measure min, max, mean, and median execution times of a function.
- Support for context-based cancellation.
- Provides detailed timing statistics.
## Installation
To install the `timingbench` package, use the following command:
```sh
go get git.eeqj.de/sneak/timingbench
```
## Usage
Here's a simple example of how to use the `timingbench` module:
```go
package main
import (
"context"
"fmt"
"math/rand"
"time"
"git.eeqj.de/sneak/timingbench"
)
// Example function to benchmark
func sampleFunction() error {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
return nil
}
func main() {
// Seed the random number generator.
rand.Seed(time.Now().UnixNano())
// Define the number of iterations.
iterations := 10
// Create a context with a timeout for cancellation.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// Measure the execution times of the sample function.
result, err := timingbench.TimeFunction(ctx, sampleFunction, iterations)
if err != nil {
fmt.Printf("Error measuring function: %v\n", err)
return
}
// Print the timing results.
fmt.Println(result.String())
}
```
## TimingResult
The `TimingResult` struct holds the timing statistics for the function executions:
```go
type TimingResult struct {
Min time.Duration // Minimum execution time
Max time.Duration // Maximum execution time
Mean time.Duration // Mean execution time
Median time.Duration // Median execution time
StartTime time.Time // Start time of the benchmarking
EndTime time.Time // End time of the benchmarking
Duration time.Duration // Total duration of the benchmarking
Iterations int // Number of iterations
}
```
## License
This project is licensed under the WTFPL License. See the
[LICENSE](./LICENSE) file for details.
## Contributing
Contributions are welcome! Please send patches via email.
## Author
`timingbench` was written by [sneak](mailto:sneak@sneak.berlin) ([website](https://sneak.berlin)).

77
bench.go Normal file
View File

@ -0,0 +1,77 @@
package timingbench
import (
"context"
"errors"
"fmt"
"sort"
"time"
"gonum.org/v1/gonum/stat"
)
// TimingResult holds the timing results for a function execution.
type TimingResult struct {
Min time.Duration
Max time.Duration
Mean time.Duration
Median time.Duration
StartTime time.Time
EndTime time.Time
Duration time.Duration
Iterations int
}
// String returns a formatted string representation of the TimingResult.
func (r TimingResult) String() string {
return fmt.Sprintf(
"Start Time: %v, End Time: %v, Duration: %v, Iterations: %d, Min: %v, Max: %v, Mean: %v, Median: %v",
r.StartTime, r.EndTime, r.Duration, r.Iterations, r.Min, r.Max, r.Mean, r.Median,
)
}
// TimeFunction runs the given function fn a specified number of times and returns the timing results.
// It supports context for cancellation.
func TimeFunction(ctx context.Context, fn func() error, iterations int) (TimingResult, error) {
if iterations <= 0 {
return TimingResult{}, errors.New("iterations must be greater than 0")
}
times := make([]float64, 0, iterations)
startTime := time.Now().UTC()
for i := 0; i < iterations; i++ {
select {
case <-ctx.Done():
return TimingResult{}, ctx.Err()
default:
iterStart := time.Now().UTC()
if err := fn(); err != nil {
return TimingResult{}, err
}
elapsed := time.Since(iterStart)
times = append(times, float64(elapsed))
}
}
endTime := time.Now().UTC()
totalDuration := endTime.Sub(startTime)
sort.Float64s(times)
min := time.Duration(times[0])
max := time.Duration(times[len(times)-1])
mean := time.Duration(stat.Mean(times, nil))
median := time.Duration(stat.Quantile(0.5, stat.Empirical, times, nil))
return TimingResult{
Min: min,
Max: max,
Mean: mean,
Median: median,
StartTime: startTime,
EndTime: endTime,
Duration: totalDuration,
Iterations: iterations,
}, nil
}

129
bench_test.go Normal file
View File

@ -0,0 +1,129 @@
package timingbench
import (
"context"
"math/rand"
"testing"
"time"
)
// isPrime checks if a number is a prime number.
func isPrime(n int) bool {
if n <= 1 {
return false
}
for i := 2; i*i <= n; i++ {
if n%i == 0 {
return false
}
}
return true
}
// randomSleep introduces a random delay between 1 and 20 milliseconds.
func randomSleep() {
time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond)
}
// generatePrimes generates prime numbers up to a given limit.
func generatePrimes(limit int) []int {
primes := []int{}
for i := 2; i <= limit; i++ {
if isPrime(i) {
primes = append(primes, i)
}
}
return primes
}
// sortRandomSlice generates a slice with random integers and sorts it.
func sortRandomSlice(size int) {
slice := make([]int, size)
for i := range slice {
slice[i] = rand.Intn(10000)
}
quickSort(slice)
}
// quickSort sorts a slice of integers using the quicksort algorithm.
func quickSort(arr []int) {
if len(arr) < 2 {
return
}
left, right := 0, len(arr)-1
pivotIndex := rand.Int() % len(arr)
arr[pivotIndex], arr[right] = arr[right], arr[pivotIndex]
for i := range arr {
if arr[i] < arr[right] {
arr[i], arr[left] = arr[left], arr[i]
left++
}
}
arr[left], arr[right] = arr[right], arr[left]
quickSort(arr[:left])
quickSort(arr[left+1:])
}
// multiplyMatrices performs matrix multiplication of two randomly generated matrices.
func multiplyMatrices(size int) {
matrixA := make([][]int, size)
matrixB := make([][]int, size)
result := make([][]int, size)
for i := 0; i < size; i++ {
matrixA[i] = make([]int, size)
matrixB[i] = make([]int, size)
result[i] = make([]int, size)
for j := 0; j < size; j++ {
matrixA[i][j] = rand.Intn(100)
matrixB[i][j] = rand.Intn(100)
}
}
for i := 0; i < size; i++ {
for j := 0; j < size; j++ {
sum := 0
for k := 0; k < size; k++ {
sum += matrixA[i][k] * matrixB[k][j]
}
result[i][j] = sum
}
}
}
// sampleFunction performs a mix of different operations to introduce variability in runtime.
func sampleFunction() error {
randomSleep()
generatePrimes(500 + rand.Intn(1500))
sortRandomSlice(500 + rand.Intn(1500))
multiplyMatrices(20 + rand.Intn(10))
return nil
}
func TestTimeFunction(t *testing.T) {
// Seed the random number generator.
rand.Seed(time.Now().UnixNano())
// Define the number of iterations.
iterations := 1000
// Create a context with a timeout for cancellation.
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
// Measure the execution times of the sample function.
result, err := TimeFunction(ctx, sampleFunction, iterations)
if err != nil {
t.Fatalf("Error measuring function: %v", err)
}
// Print the timing results.
t.Logf(result.String())
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module git.eeqj.de/sneak/timingbench
go 1.22.2
require gonum.org/v1/gonum v0.15.0 // indirect

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=