mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
254 lines
7.4 KiB
V
254 lines
7.4 KiB
V
module benchmark
|
|
|
|
import time
|
|
import math
|
|
import sync
|
|
|
|
// Benchmark represent all significant data for benchmarking. Provide clear way for getting result in convinient way by exported methods
|
|
@[noinit]
|
|
pub struct Benchmark {
|
|
pub mut:
|
|
n i64 // Number of iterations. Set explicitly or computed from expected time of benchmarking
|
|
bench_func fn () ! @[required] // function for benchmarking
|
|
bench_time time.Duration // benchmark duration
|
|
is_parallel bool // if true every bench_func run in separate coroutine
|
|
benchmark_result BenchmarkResult // accumulator of benchmark metrics
|
|
timer_on bool // inner flag of time recording
|
|
start_time time.Time // start timestamp of timer
|
|
duration time.Duration // expected time of benchmark process
|
|
failed bool // flag of bench_func failure. true if one of bench_func run failed
|
|
start_memory usize // memory status on start benchmark
|
|
start_allocs usize // size of object allocated on heap
|
|
}
|
|
|
|
// BenchmarkDefaults is params struct for providing parameters of benchmarking to setup function
|
|
// - n - number of iterations. set if you know how many runs of function you need. if you don't know how many you need - set 0
|
|
// - duration - by default 1s. expecting duration of all benchmark runs. doesn't work if is_parallel == true
|
|
// - is_parallel - if true, every bench_func run in separate coroutine
|
|
@[params]
|
|
pub struct BenchmarkDefaults {
|
|
pub:
|
|
duration time.Duration = time.second
|
|
is_parallel bool
|
|
n i64
|
|
}
|
|
|
|
// Benchmark.new - constructor of benchmark
|
|
// arguments:
|
|
// - bench_func - function to benchmark. required, if you have no function - you don't need benchmark
|
|
// - params - structure of benchmark parameters
|
|
pub fn setup(bench_func fn () !, params BenchmarkDefaults) !Benchmark {
|
|
if bench_func == unsafe { nil } {
|
|
return error('Benchmark function cannot be `nil`')
|
|
}
|
|
|
|
if params.duration > 0 && params.is_parallel {
|
|
return error('can not predict number of parallel iterations')
|
|
}
|
|
|
|
return Benchmark{
|
|
n: params.n
|
|
bench_func: bench_func
|
|
bench_time: params.duration
|
|
is_parallel: params.is_parallel
|
|
}
|
|
}
|
|
|
|
// run_benchmark - function for start benchmarking
|
|
// run benchmark n times, or duration time
|
|
pub fn (mut b Benchmark) run() {
|
|
// run bench_func one time for heat up processor cache and get elapsed time for n prediction
|
|
b.run_n(1)
|
|
|
|
// if one iteration failed no need to do more
|
|
if b.failed {
|
|
b.n = 1
|
|
// show failed result. bad result is steel result
|
|
b.benchmark_result.print()
|
|
}
|
|
|
|
// if n is provided we should run exactly n times. but 1 time we already run
|
|
if b.n > 1 {
|
|
b.run_n(b.n - 1)
|
|
}
|
|
|
|
// if n is zero then we should run bench_func enough time for estimate duration time of execution
|
|
if b.n == 0 {
|
|
b.n = 1
|
|
// if one of runs failed - bench_func is not valid
|
|
// but 1e9 times of evaluation is too much
|
|
// so we need to repeat prediction-execition process while elapsed time less then expected time
|
|
for !b.failed && b.duration < b.bench_time && b.n < 1000000000 {
|
|
// we need predict new amount of executions to estimate expected time
|
|
n := b.predict_n()
|
|
|
|
// later we predict how many runs we need yet. so we run predicted times
|
|
b.run_n(n)
|
|
b.n += n
|
|
}
|
|
}
|
|
|
|
// if n is provided, duration will be calculated. otherwise n will
|
|
b.benchmark_result.n = b.n
|
|
b.benchmark_result.t = b.duration
|
|
|
|
// despite of the way of usage of benchmark result(send py api, send to chat, process, logging, etc), we print it
|
|
b.benchmark_result.print()
|
|
}
|
|
|
|
// run_n - run bench_func n times
|
|
fn (mut b Benchmark) run_n(n i64) {
|
|
// clear memory for avoid GC influence
|
|
gc_collect()
|
|
|
|
// reset and start timer for get elapsed time
|
|
b.reset_timer()
|
|
b.start_timer()
|
|
|
|
// unwrap function from struct field
|
|
mut f := b.bench_func
|
|
|
|
if !b.is_parallel {
|
|
// run n times consistently
|
|
for i := i64(0); i < n; i++ {
|
|
f() or {
|
|
// if one execution failed print err, set failed flag and stop execution
|
|
b.failed = true
|
|
// workaround for consider unsuccesful runs
|
|
b.n -= n - i
|
|
eprintln('Error: ${err}')
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// spawn n coroutines, wait end of spawning and unpause all coroutines
|
|
if b.is_parallel {
|
|
// WaitGroup for spawn and pause enough coroutines
|
|
mut spawnwg := sync.new_waitgroup()
|
|
spawnwg.add(int(n))
|
|
// WaitGroup for wait of end of execution
|
|
mut workwg := sync.new_waitgroup()
|
|
workwg.add(int(n))
|
|
|
|
for i := i64(0); i < n; i++ {
|
|
spawn run_in_one_time(mut workwg, mut spawnwg, f)
|
|
spawnwg.done()
|
|
}
|
|
workwg.wait()
|
|
}
|
|
|
|
// stop timer and collect data
|
|
b.stop_timer()
|
|
}
|
|
|
|
fn run_in_one_time(mut workwg sync.WaitGroup, mut spawnwg sync.WaitGroup, f fn () !) {
|
|
defer {
|
|
workwg.done()
|
|
}
|
|
spawnwg.wait()
|
|
f() or { return } // TODO: add error handling
|
|
}
|
|
|
|
// predict_n - predict number of executions to estimate duration
|
|
// based on previous values
|
|
fn (mut b Benchmark) predict_n() i64 {
|
|
// goal duration in nanoseconds
|
|
mut goal_ns := b.bench_time.nanoseconds()
|
|
// get number of previous iterations
|
|
prev_iters := b.n
|
|
// get elapsed time in nanoseconds
|
|
mut prev_ns := b.duration.nanoseconds()
|
|
|
|
// to avoid division by zero
|
|
if prev_ns <= 0 {
|
|
prev_ns = 1
|
|
}
|
|
|
|
// multiple first to avoid division with less then 0 result
|
|
mut n := goal_ns * prev_iters
|
|
n = n / prev_ns
|
|
// grow at least in 1.2
|
|
n += n / 5
|
|
|
|
// to not grow to fast
|
|
n = math.min(n, 100 * b.n)
|
|
// to grow at least on 1
|
|
n = math.max(n, b.n + 1)
|
|
// to avoid run more then 1e9 times
|
|
n = math.min(n, 1000000000)
|
|
|
|
return n
|
|
}
|
|
|
|
// reset_timer - clear timer and reset memory start data
|
|
fn (mut b Benchmark) reset_timer() {
|
|
// if timer_on we should restart it
|
|
if b.timer_on {
|
|
b.start_time = time.now()
|
|
b.start_memory = gc_memory_use()
|
|
b.start_allocs = gc_heap_usage().bytes_since_gc
|
|
}
|
|
}
|
|
|
|
// starttimer - register start measures of memory
|
|
fn (mut b Benchmark) start_timer() {
|
|
// you do not need to start timer that already started
|
|
if !b.timer_on {
|
|
b.start_time = time.now()
|
|
b.start_memory = gc_memory_use()
|
|
b.start_allocs = gc_heap_usage().bytes_since_gc
|
|
b.timer_on = true
|
|
}
|
|
}
|
|
|
|
// stop_timer - accumulate menchmark data
|
|
fn (mut b Benchmark) stop_timer() {
|
|
if b.timer_on {
|
|
// accumulate delta time of execution
|
|
b.duration += time.since(b.start_time)
|
|
// accumulate memory growth
|
|
b.benchmark_result.mem += gc_memory_use() - b.start_memory
|
|
// accumulate heap usage
|
|
b.benchmark_result.allocs += gc_heap_usage().bytes_since_gc - b.start_allocs
|
|
b.timer_on = false
|
|
}
|
|
}
|
|
|
|
// BenchmarkResult - struct for represent result of benchmark
|
|
struct BenchmarkResult {
|
|
pub mut:
|
|
n i64 // iterations count
|
|
t time.Duration // elapsed time
|
|
mem usize // all allocated memory
|
|
allocs usize // heap allocated memory
|
|
}
|
|
|
|
// ns_per_op - elapsed time in nanoseconds per iteration
|
|
fn (r BenchmarkResult) ns_per_op() i64 {
|
|
if r.n <= 0 {
|
|
return 0
|
|
}
|
|
return r.t.nanoseconds() / i64(r.n)
|
|
}
|
|
|
|
// allocs_per_op - heap usage per iteration
|
|
fn (r BenchmarkResult) allocs_per_op() i64 {
|
|
if r.n <= 0 {
|
|
return 0
|
|
}
|
|
return i64(r.allocs) / i64(r.n)
|
|
}
|
|
|
|
// alloced_bytes_per_op - memory usage per iteration
|
|
fn (r BenchmarkResult) alloced_bytes_per_op() i64 {
|
|
if r.n <= 0 {
|
|
return 0
|
|
}
|
|
return i64(r.mem) / i64(r.n)
|
|
}
|
|
|
|
// print - all measurements
|
|
fn (r BenchmarkResult) print() {
|
|
println('Iterations: ${r.n:10}\t\tTotal Duration: ${r.t:10}\tns/op: ${r.ns_per_op():10}\tB/op: ${r.alloced_bytes_per_op():6}\tallocs/op: ${r.allocs_per_op():6}')
|
|
}
|