mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-28 17:04:34 +00:00
237 lines
6.4 KiB
Odin
237 lines
6.4 KiB
Odin
package time
|
|
|
|
import "base:runtime"
|
|
import "base:intrinsics"
|
|
|
|
/*
|
|
Type representing monotonic time, useful for measuring durations.
|
|
*/
|
|
Tick :: struct {
|
|
_nsec: i64, // relative amount
|
|
}
|
|
|
|
/*
|
|
Obtain the current tick.
|
|
*/
|
|
@(require_results)
|
|
tick_now :: proc "contextless" () -> Tick {
|
|
return _tick_now()
|
|
}
|
|
|
|
/*
|
|
Add duration to a tick.
|
|
*/
|
|
@(require_results)
|
|
tick_add :: proc "contextless" (t: Tick, d: Duration) -> Tick {
|
|
return Tick{t._nsec + i64(d)}
|
|
}
|
|
|
|
/*
|
|
Obtain the difference between ticks.
|
|
*/
|
|
@(require_results)
|
|
tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
|
|
d := end._nsec - start._nsec
|
|
return Duration(d)
|
|
}
|
|
|
|
/*
|
|
Incrementally obtain durations since last tick.
|
|
|
|
This procedure returns the duration between the current tick and the tick
|
|
stored in `prev` pointer, and then stores the current tick in location,
|
|
specified by `prev`. If the prev pointer contains an zero-initialized tick,
|
|
then the returned duration is 0.
|
|
|
|
This procedure is meant to be used in a loop, or in other scenarios, where one
|
|
might want to obtain time between multiple ticks at specific points.
|
|
*/
|
|
@(require_results)
|
|
tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
|
|
d: Duration
|
|
t := tick_now()
|
|
if prev._nsec != 0 {
|
|
d = tick_diff(prev^, t)
|
|
}
|
|
prev^ = t
|
|
return d
|
|
}
|
|
|
|
/*
|
|
Obtain the duration since last tick.
|
|
*/
|
|
@(require_results)
|
|
tick_since :: proc "contextless" (start: Tick) -> Duration {
|
|
return tick_diff(start, tick_now())
|
|
}
|
|
|
|
/*
|
|
Capture the duration the code in the current scope takes to execute.
|
|
*/
|
|
@(deferred_in_out=_tick_duration_end)
|
|
SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
|
|
return tick_now()
|
|
}
|
|
|
|
_tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
|
|
d^ = tick_since(t)
|
|
}
|
|
|
|
when ODIN_ARCH == .amd64 {
|
|
@(private)
|
|
x86_has_invariant_tsc :: proc "contextless" () -> bool {
|
|
eax, _, _, _ := intrinsics.x86_cpuid(0x80_000_000, 0)
|
|
|
|
// Is this processor *really* ancient?
|
|
if eax < 0x80_000_007 {
|
|
return false
|
|
}
|
|
|
|
// check if the invariant TSC bit is set
|
|
_, _, _, edx := intrinsics.x86_cpuid(0x80_000_007, 0)
|
|
return (edx & (1 << 8)) != 0
|
|
}
|
|
}
|
|
|
|
when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
|
|
_get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
/*
|
|
Check if the CPU has invariant TSC.
|
|
|
|
This procedure checks if the CPU contains an invariant TSC (Time stamp counter).
|
|
Invariant TSC is a feature of modern processors that allows them to run their
|
|
TSC at a fixed frequency, independent of ACPI state, and CPU frequency.
|
|
*/
|
|
@(require_results)
|
|
has_invariant_tsc :: proc "contextless" () -> bool {
|
|
when ODIN_ARCH == .amd64 {
|
|
return x86_has_invariant_tsc()
|
|
} else when ODIN_ARCH == .arm64 {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/*
|
|
Obtain the CPU's TSC frequency, in hertz.
|
|
|
|
This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU
|
|
doesn't have an invariant TSC, this procedure returns with an error. Otherwise
|
|
an attempt is made to fetch the TSC frequency from the OS. If this fails,
|
|
the frequency is obtained by sleeping for the specified amount of time and
|
|
dividing the readings from TSC by the duration of the sleep.
|
|
|
|
The duration of sleep can be controlled by `fallback_sleep` parameter.
|
|
*/
|
|
@(require_results)
|
|
tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
|
|
if !has_invariant_tsc() {
|
|
return 0, false
|
|
}
|
|
|
|
hz, ok := _get_tsc_frequency()
|
|
if !ok {
|
|
// fallback to approximate TSC
|
|
tsc_begin := intrinsics.read_cycle_counter()
|
|
tick_begin := tick_now()
|
|
|
|
sleep(fallback_sleep)
|
|
|
|
tsc_end := intrinsics.read_cycle_counter()
|
|
tick_end := tick_now()
|
|
|
|
time_diff := u128(duration_nanoseconds(tick_diff(tick_begin, tick_end)))
|
|
hz = u64((u128(tsc_end - tsc_begin) * 1_000_000_000) / time_diff)
|
|
}
|
|
|
|
return hz, true
|
|
}
|
|
|
|
// Benchmark helpers
|
|
|
|
/*
|
|
Errors returned by the `benchmark()` procedure.
|
|
*/
|
|
Benchmark_Error :: enum {
|
|
Okay = 0,
|
|
Allocation_Error,
|
|
}
|
|
|
|
/*
|
|
Options for benchmarking.
|
|
*/
|
|
Benchmark_Options :: struct {
|
|
// The initialization procedure. `benchmark()` will call this before taking measurements.
|
|
setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
|
|
// The procedure to benchmark.
|
|
bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
|
|
// The deinitialization procedure.
|
|
teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
|
|
// Field to be used by `bench()` procedure for any purpose.
|
|
rounds: int,
|
|
// Field to be used by `bench()` procedure for any purpose.
|
|
bytes: int,
|
|
// Field to be used by `bench()` procedure for any purpose.
|
|
input: []u8,
|
|
// `bench()` writes to specify the count of elements processed.
|
|
count: int,
|
|
// `bench()` writes to specify the number of bytes processed.
|
|
processed: int,
|
|
// `bench()` can write the output slice here.
|
|
output: []u8, // Unused for hash benchmarks
|
|
// `bench()` can write the output hash here.
|
|
hash: u128,
|
|
// `benchmark()` procedure will output the duration of benchmark
|
|
duration: Duration,
|
|
// `benchmark()` procedure will output the average count of elements
|
|
// processed per second, using the `count` field of this struct.
|
|
rounds_per_second: f64,
|
|
// `benchmark()` procedure will output the average number of megabytes
|
|
// processed per second, using the `processed` field of this struct.
|
|
megabytes_per_second: f64,
|
|
}
|
|
|
|
/*
|
|
Benchmark a procedure.
|
|
|
|
This procedure produces a benchmark. The procedure specified in the `bench`
|
|
field of the `options` parameter will be benchmarked. The following metrics
|
|
can be obtained:
|
|
|
|
- Run time of the procedure
|
|
- Number of elements per second processed on average
|
|
- Number of bytes per second this processed on average
|
|
|
|
In order to obtain these metrics, the `bench()` procedure writes to `options`
|
|
struct the number of elements or bytes it has processed.
|
|
*/
|
|
benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
|
|
assert(options != nil)
|
|
assert(options.bench != nil)
|
|
|
|
if options.setup != nil {
|
|
options->setup(allocator) or_return
|
|
}
|
|
|
|
diff: Duration
|
|
{
|
|
SCOPED_TICK_DURATION(&diff)
|
|
options->bench(allocator) or_return
|
|
}
|
|
options.duration = diff
|
|
|
|
times_per_second := f64(Second) / f64(diff)
|
|
options.rounds_per_second = times_per_second * f64(options.count)
|
|
options.megabytes_per_second = f64(options.processed) / f64(1024 * 1024) * times_per_second
|
|
|
|
if options.teardown != nil {
|
|
options->teardown(allocator) or_return
|
|
}
|
|
return
|
|
}
|