Browse Source

Add xxhash benchmark.

Jeroen van Rijn 4 years ago
parent
commit
f16e98b074
3 changed files with 145 additions and 2 deletions
  1. 86 1
      core/hash/xxhash/common.odin
  2. 57 0
      core/time/perf.odin
  3. 2 1
      core/time/time_windows.odin

+ 86 - 1
core/hash/xxhash/common.odin

@@ -11,6 +11,10 @@ package xxhash
 
 import "core:intrinsics"
 import "core:runtime"
+import "core:time"
+import "core:fmt"
+import "core:testing"
+
 mem_copy :: runtime.mem_copy
 
 /*
@@ -75,4 +79,85 @@ XXH64_read64 :: #force_inline proc(buf: []u8, alignment: Alignment) -> (res: u64
 		mem_copy(&b, raw_data(buf[:]), 8)
 		return u64(b)
 	}
-}
+}
+
+
+/*
+	Benchmarks
+*/
+
+setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	assert(options != nil)
+
+	options.input = make([]u8, options.bytes, allocator)
+	return nil if len(options.input) == options.bytes else .Allocation_Error
+}
+
+teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	assert(options != nil)
+
+	delete(options.input)
+	return nil
+}
+
+benchmark_xxhash32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	for _ in 0..=options.rounds {
+		_ = XXH32(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	return nil
+}
+
+benchmark_xxhash64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	for _ in 0..=options.rounds {
+		_ = XXH64(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	return nil
+}
+
+benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) {
+	fmt.printf("\t[%v] %v rounds, %v bytes procesed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n",
+		name,
+		options.rounds,
+		options.processed,
+		time.duration_nanoseconds(options.duration),
+		options.rounds_per_second,
+		options.megabytes_per_second,
+	)
+}
+
+@test
+benchmark_runner :: proc(t: ^testing.T) {
+	fmt.println("Starting benchmarks:")
+
+	options := &time.Benchmark_Options{
+		rounds   = 1_000,
+		bytes    = 100,
+		setup    = setup_xxhash,
+		bench    = benchmark_xxhash32,
+		teardown = teardown_xxhash,
+	}
+	err := time.benchmark(options, context.allocator)
+	benchmark_print("xxhash32 100 bytes", options)
+
+	options.bytes = 1_000_000
+	err = time.benchmark(options, context.allocator)
+	benchmark_print("xxhash32 1_000_000 bytes", options)
+
+	options.bytes  = 100
+	options.bench = benchmark_xxhash64
+	err = time.benchmark(options, context.allocator)
+	benchmark_print("xxhash64 100 bytes", options)
+
+	options.bytes = 1_000_000
+	err = time.benchmark(options, context.allocator)
+	benchmark_print("xxhash64 1_000_000 bytes", options)
+}
+

+ 57 - 0
core/time/perf.odin

@@ -1,5 +1,7 @@
 package time
 
+import "core:mem"
+
 Tick :: struct {
 	_nsec: i64, // relative amount
 }
@@ -37,3 +39,58 @@ SCOPED_TICK_DURATION :: proc(d: ^Duration) -> Tick {
 _tick_duration_end :: proc(d: ^Duration, t: Tick) {
 	d^ = tick_since(t)
 }
+
+/*
+	Benchmark helpers
+*/
+
+Benchmark_Error :: enum {
+	Okay = 0,
+	Allocation_Error,
+}
+
+Benchmark_Options :: struct {
+	setup:     #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error),
+	bench:     #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error),
+	teardown:  #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error),
+
+	rounds:    int,
+	bytes:     int,
+	input:     []u8,
+
+	count:     int,
+	processed: int,
+	output:    []u8,
+
+	/*
+		Performance
+	*/
+	duration:             Duration,
+	rounds_per_second:    f64,
+	megabytes_per_second: f64,
+}
+
+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
+}

+ 2 - 1
core/time/time_windows.odin

@@ -24,7 +24,8 @@ _tick_now :: proc() -> Tick {
 		return q * num + r * num / den
 	}
 
-	@thread_local qpc_frequency: win32.LARGE_INTEGER
+	// @thread_local qpc_frequency: win32.LARGE_INTEGER
+	qpc_frequency: win32.LARGE_INTEGER
 
 	if qpc_frequency == 0 {
 		win32.QueryPerformanceFrequency(&qpc_frequency)