123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- 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.
- */
- tick_now :: proc "contextless" () -> Tick {
- return _tick_now()
- }
- /*
- Obtain the difference between ticks.
- */
- 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.
- */
- 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.
- */
- 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.
- */
- has_invariant_tsc :: proc "contextless" () -> bool {
- when ODIN_ARCH == .amd64 {
- return x86_has_invariant_tsc()
- }
- 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.
- */
- 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
- }
|