2
0

perf.odin 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package time
  2. import "base:runtime"
  3. import "base:intrinsics"
  4. /*
  5. Type representing monotonic time, useful for measuring durations.
  6. */
  7. Tick :: struct {
  8. _nsec: i64, // relative amount
  9. }
  10. /*
  11. Obtain the current tick.
  12. */
  13. tick_now :: proc "contextless" () -> Tick {
  14. return _tick_now()
  15. }
  16. /*
  17. Obtain the difference between ticks.
  18. */
  19. tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
  20. d := end._nsec - start._nsec
  21. return Duration(d)
  22. }
  23. /*
  24. Incrementally obtain durations since last tick.
  25. This procedure returns the duration between the current tick and the tick
  26. stored in `prev` pointer, and then stores the current tick in location,
  27. specified by `prev`. If the prev pointer contains an zero-initialized tick,
  28. then the returned duration is 0.
  29. This procedure is meant to be used in a loop, or in other scenarios, where one
  30. might want to obtain time between multiple ticks at specific points.
  31. */
  32. tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
  33. d: Duration
  34. t := tick_now()
  35. if prev._nsec != 0 {
  36. d = tick_diff(prev^, t)
  37. }
  38. prev^ = t
  39. return d
  40. }
  41. /*
  42. Obtain the duration since last tick.
  43. */
  44. tick_since :: proc "contextless" (start: Tick) -> Duration {
  45. return tick_diff(start, tick_now())
  46. }
  47. /*
  48. Capture the duration the code in the current scope takes to execute.
  49. */
  50. @(deferred_in_out=_tick_duration_end)
  51. SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
  52. return tick_now()
  53. }
  54. _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
  55. d^ = tick_since(t)
  56. }
  57. when ODIN_ARCH == .amd64 {
  58. @(private)
  59. x86_has_invariant_tsc :: proc "contextless" () -> bool {
  60. eax, _, _, _ := intrinsics.x86_cpuid(0x80_000_000, 0)
  61. // Is this processor *really* ancient?
  62. if eax < 0x80_000_007 {
  63. return false
  64. }
  65. // check if the invariant TSC bit is set
  66. _, _, _, edx := intrinsics.x86_cpuid(0x80_000_007, 0)
  67. return (edx & (1 << 8)) != 0
  68. }
  69. }
  70. when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD {
  71. _get_tsc_frequency :: proc "contextless" () -> (u64, bool) {
  72. return 0, false
  73. }
  74. }
  75. /*
  76. Check if the CPU has invariant TSC.
  77. This procedure checks if the CPU contains an invariant TSC (Time stamp counter).
  78. Invariant TSC is a feature of modern processors that allows them to run their
  79. TSC at a fixed frequency, independent of ACPI state, and CPU frequency.
  80. */
  81. has_invariant_tsc :: proc "contextless" () -> bool {
  82. when ODIN_ARCH == .amd64 {
  83. return x86_has_invariant_tsc()
  84. }
  85. return false
  86. }
  87. /*
  88. Obtain the CPU's TSC frequency, in hertz.
  89. This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU
  90. doesn't have an invariant TSC, this procedure returns with an error. Otherwise
  91. an attempt is made to fetch the TSC frequency from the OS. If this fails,
  92. the frequency is obtained by sleeping for the specified amount of time and
  93. dividing the readings from TSC by the duration of the sleep.
  94. The duration of sleep can be controlled by `fallback_sleep` parameter.
  95. */
  96. tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
  97. if !has_invariant_tsc() {
  98. return 0, false
  99. }
  100. hz, ok := _get_tsc_frequency()
  101. if !ok {
  102. // fallback to approximate TSC
  103. tsc_begin := intrinsics.read_cycle_counter()
  104. tick_begin := tick_now()
  105. sleep(fallback_sleep)
  106. tsc_end := intrinsics.read_cycle_counter()
  107. tick_end := tick_now()
  108. time_diff := u128(duration_nanoseconds(tick_diff(tick_begin, tick_end)))
  109. hz = u64((u128(tsc_end - tsc_begin) * 1_000_000_000) / time_diff)
  110. }
  111. return hz, true
  112. }
  113. // Benchmark helpers
  114. /*
  115. Errors returned by the `benchmark()` procedure.
  116. */
  117. Benchmark_Error :: enum {
  118. Okay = 0,
  119. Allocation_Error,
  120. }
  121. /*
  122. Options for benchmarking.
  123. */
  124. Benchmark_Options :: struct {
  125. // The initialization procedure. `benchmark()` will call this before taking measurements.
  126. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
  127. // The procedure to benchmark.
  128. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
  129. // The deinitialization procedure.
  130. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
  131. // Field to be used by `bench()` procedure for any purpose.
  132. rounds: int,
  133. // Field to be used by `bench()` procedure for any purpose.
  134. bytes: int,
  135. // Field to be used by `bench()` procedure for any purpose.
  136. input: []u8,
  137. // `bench()` writes to specify the count of elements processed.
  138. count: int,
  139. // `bench()` writes to specify the number of bytes processed.
  140. processed: int,
  141. // `bench()` can write the output slice here.
  142. output: []u8, // Unused for hash benchmarks
  143. // `bench()` can write the output hash here.
  144. hash: u128,
  145. // `benchmark()` procedure will output the duration of benchmark
  146. duration: Duration,
  147. // `benchmark()` procedure will output the average count of elements
  148. // processed per second, using the `count` field of this struct.
  149. rounds_per_second: f64,
  150. // `benchmark()` procedure will output the average number of megabytes
  151. // processed per second, using the `processed` field of this struct.
  152. megabytes_per_second: f64,
  153. }
  154. /*
  155. Benchmark a procedure.
  156. This procedure produces a benchmark. The procedure specified in the `bench`
  157. field of the `options` parameter will be benchmarked. The following metrics
  158. can be obtained:
  159. - Run time of the procedure
  160. - Number of elements per second processed on average
  161. - Number of bytes per second this processed on average
  162. In order to obtain these metrics, the `bench()` procedure writes to `options`
  163. struct the number of elements or bytes it has processed.
  164. */
  165. benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
  166. assert(options != nil)
  167. assert(options.bench != nil)
  168. if options.setup != nil {
  169. options->setup(allocator) or_return
  170. }
  171. diff: Duration
  172. {
  173. SCOPED_TICK_DURATION(&diff)
  174. options->bench(allocator) or_return
  175. }
  176. options.duration = diff
  177. times_per_second := f64(Second) / f64(diff)
  178. options.rounds_per_second = times_per_second * f64(options.count)
  179. options.megabytes_per_second = f64(options.processed) / f64(1024 * 1024) * times_per_second
  180. if options.teardown != nil {
  181. options->teardown(allocator) or_return
  182. }
  183. return
  184. }