Browse Source

test/benchmarks/crypto: Improve benchmarks

- Use text/table for results
- Add more benchmarks
Yawning Angel 10 months ago
parent
commit
f3f5fbd373

+ 96 - 0
tests/benchmark/crypto/benchmark_aead.odin

@@ -0,0 +1,96 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:crypto"
+import "core:testing"
+import "core:text/table"
+import "core:time"
+
+import "core:crypto/aead"
+
+@(private = "file")
+ITERS :: 10000
+@(private = "file")
+SIZES := []int{64, 1024, 65536}
+
+@(test)
+benchmark_crypto_aead :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "AEAD")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Size", "Time", "Throughput")
+
+	for algo, i in aead.Algorithm {
+		if algo == .Invalid {
+			continue
+		}
+		if i > 1 {
+			table.row(&tbl)
+		}
+
+		algo_name := aead.ALGORITHM_NAMES[algo]
+		key_sz := aead.KEY_SIZES[algo]
+
+		key := make([]byte, key_sz, context.temp_allocator)
+		crypto.rand_bytes(key)
+
+		// TODO: Benchmark all available imlementations?
+		ctx: aead.Context
+		aead.init(&ctx, algo, key)
+
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = aead.IV_SIZES[algo] + sz,
+				setup = setup_sized_buf,
+				bench = do_bench_aead,
+				teardown = teardown_sized_buf,
+			}
+			context.user_ptr = &ctx
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				algo_name,
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+do_bench_aead :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	tag_: [aead.MAX_TAG_SIZE]byte
+
+	ctx := (^aead.Context)(context.user_ptr)
+	iv_sz := aead.iv_size(ctx)
+
+	iv := options.input[:iv_sz]
+	buf := options.input[iv_sz:]
+	tag := tag_[:aead.tag_size(ctx)]
+
+	for _ in 0 ..= options.rounds {
+		aead.seal_ctx(ctx, buf, tag, iv, nil, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * (options.bytes - iv_sz)
+
+	return
+}

+ 0 - 552
tests/benchmark/crypto/benchmark_crypto.odin

@@ -1,552 +0,0 @@
-package benchmark_core_crypto
-
-import "base:runtime"
-import "core:encoding/hex"
-import "core:fmt"
-import "core:log"
-import "core:strings"
-import "core:testing"
-import "core:time"
-
-import "core:crypto/aegis"
-import "core:crypto/aes"
-import "core:crypto/chacha20"
-import "core:crypto/chacha20poly1305"
-import "core:crypto/deoxysii"
-import "core:crypto/ed25519"
-import "core:crypto/poly1305"
-import "core:crypto/x25519"
-import "core:crypto/x448"
-
-// Cryptographic primitive benchmarks.
-
-@(test)
-benchmark_crypto :: proc(t: ^testing.T) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-
-	str: strings.Builder
-	strings.builder_init(&str, context.allocator)
-	defer {
-		log.info(strings.to_string(str))
-		strings.builder_destroy(&str)
-	}
-
-	{
-		name := "AES256-CTR 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_aes256_ctr,
-			teardown = _teardown_sized_buf,
-		}
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AES256-CTR 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AES256-CTR 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "ChaCha20 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_chacha20,
-			teardown = _teardown_sized_buf,
-		}
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "ChaCha20 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "ChaCha20 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "Poly1305 64 zero bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_poly1305,
-			teardown = _teardown_sized_buf,
-		}
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "Poly1305 1024 zero bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "chacha20poly1305 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_chacha20poly1305,
-			teardown = _teardown_sized_buf,
-		}
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "chacha20poly1305 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "chacha20poly1305 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "AES256-GCM 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_aes256_gcm,
-			teardown = _teardown_sized_buf,
-		}
-
-		key := [aes.KEY_SIZE_256]byte {
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		}
-		ctx: aes.Context_GCM
-		aes.init_gcm(&ctx, key[:])
-
-		context.user_ptr = &ctx
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AES256-GCM 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AES256-GCM 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "AEGIS-256 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_aegis_256,
-			teardown = _teardown_sized_buf,
-		}
-
-		key := [aegis.KEY_SIZE_256]byte {
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		}
-		ctx: aegis.Context
-		aegis.init(&ctx, key[:])
-
-		context.user_ptr = &ctx
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AEGIS-256 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "AEGIS-256 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		name := "Deoxys-II-256 64 bytes"
-		options := &time.Benchmark_Options {
-			rounds = 1_000,
-			bytes = 64,
-			setup = _setup_sized_buf,
-			bench = _benchmark_deoxysii_256,
-			teardown = _teardown_sized_buf,
-		}
-
-		key := [aegis.KEY_SIZE_256]byte {
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		}
-		ctx: deoxysii.Context
-		deoxysii.init(&ctx, key[:])
-
-		context.user_ptr = &ctx
-
-		err := time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "Deoxys-II-256 1024 bytes"
-		options.bytes = 1024
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-
-		name = "Deoxys-II-256 65536 bytes"
-		options.bytes = 65536
-		err = time.benchmark(options, context.allocator)
-		testing.expect(t, err == nil, name)
-		benchmark_print(&str, name, options)
-	}
-	{
-		iters :: 10000
-
-		priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
-		priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator)
-		priv_key: ed25519.Private_Key
-		start := time.now()
-		for i := 0; i < iters; i = i + 1 {
-			ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes)
-			assert(ok, "private key should deserialize")
-		}
-		elapsed := time.since(start)
-		fmt.sbprintfln(&str,
-			"ed25519.private_key_set_bytes: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		)
-
-		pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing"
-		pub_key: ed25519.Public_Key
-		start = time.now()
-		for i := 0; i < iters; i = i + 1 {
-			ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:])
-			assert(ok, "public key should deserialize")
-		}
-		elapsed = time.since(start)
-		fmt.sbprintfln(&str,
-			"ed25519.public_key_set_bytes: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		)
-
-		msg := "Got a job for you, 621."
-		sig_bytes: [ed25519.SIGNATURE_SIZE]byte
-		msg_bytes := transmute([]byte)(msg)
-		start = time.now()
-		for i := 0; i < iters; i = i + 1 {
-			ed25519.sign(&priv_key, msg_bytes, sig_bytes[:])
-		}
-		elapsed = time.since(start)
-		fmt.sbprintfln(&str,
-		    "ed25519.sign: ~%f us/op",
-		    time.duration_microseconds(elapsed) / iters,
-		)
-
-		start = time.now()
-		for i := 0; i < iters; i = i + 1 {
-			ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:])
-			assert(ok, "signature should validate")
-		}
-		elapsed = time.since(start)
-		fmt.sbprintfln(&str,
-			"ed25519.verify: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		)
-	}
-	{
-		point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
-		scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
-
-		point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
-		scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
-		out: [x25519.POINT_SIZE]byte = ---
-
-		iters :: 10000
-		start := time.now()
-		for i := 0; i < iters; i = i + 1 {
-			x25519.scalarmult(out[:], scalar[:], point[:])
-		}
-		elapsed := time.since(start)
-
-		fmt.sbprintfln(&str,
-			"x25519.scalarmult: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		)
-	}
-	{
-		point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
-		scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
-
-		point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
-		scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
-		out: [x448.POINT_SIZE]byte = ---
-
-		iters :: 10000
-		start := time.now()
-		for i := 0; i < iters; i = i + 1 {
-			x448.scalarmult(out[:], scalar[:], point[:])
-		}
-		elapsed := time.since(start)
-
-		fmt.sbprintfln(&str,
-			"x448.scalarmult: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		)
-	}
-}
-
-@(private)
-_setup_sized_buf :: 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
-}
-
-@(private)
-_teardown_sized_buf :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	assert(options != nil)
-
-	delete(options.input)
-	return nil
-}
-
-@(private)
-_benchmark_chacha20 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [chacha20.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	iv := [chacha20.IV_SIZE]byte {
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x00,
-	}
-
-	ctx: chacha20.Context = ---
-	chacha20.init(&ctx, key[:], iv[:])
-
-	for _ in 0 ..= options.rounds {
-		chacha20.xor_bytes(&ctx, buf, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-@(private)
-_benchmark_poly1305 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [poly1305.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-
-	tag: [poly1305.TAG_SIZE]byte = ---
-	for _ in 0 ..= options.rounds {
-		poly1305.sum(tag[:], buf, key[:])
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	//options.hash      = u128(h)
-	return nil
-}
-
-@(private)
-_benchmark_chacha20poly1305 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [chacha20.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	iv := [chacha20.IV_SIZE]byte {
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x00,
-	}
-
-	ctx: chacha20poly1305.Context = ---
-	chacha20poly1305.init(&ctx, key[:]) // Basically 0 overhead.
-
-	tag: [chacha20poly1305.TAG_SIZE]byte = ---
-
-	for _ in 0 ..= options.rounds {
-		chacha20poly1305.seal(&ctx, buf, tag[:], iv[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-@(private)
-_benchmark_aes256_ctr :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [aes.KEY_SIZE_256]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	iv := [aes.CTR_IV_SIZE]byte {
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	}
-
-	ctx: aes.Context_CTR = ---
-	aes.init_ctr(&ctx, key[:], iv[:])
-
-	for _ in 0 ..= options.rounds {
-		aes.xor_bytes_ctr(&ctx, buf, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-_benchmark_aes256_gcm :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	iv: [aes.GCM_IV_SIZE]byte
-	tag: [aes.GCM_TAG_SIZE]byte = ---
-
-	ctx := (^aes.Context_GCM)(context.user_ptr)
-
-	for _ in 0 ..= options.rounds {
-		aes.seal_gcm(ctx, buf, tag[:], iv[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-_benchmark_aegis_256 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	iv: [aegis.IV_SIZE_256]byte
-	tag: [aegis.TAG_SIZE_128]byte = ---
-
-	ctx := (^aegis.Context)(context.user_ptr)
-
-	for _ in 0 ..= options.rounds {
-		aegis.seal(ctx, buf, tag[:], iv[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-_benchmark_deoxysii_256 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	iv: [deoxysii.IV_SIZE]byte
-	tag: [deoxysii.TAG_SIZE]byte = ---
-
-	ctx := (^deoxysii.Context)(context.user_ptr)
-
-	for _ in 0 ..= options.rounds {
-		deoxysii.seal(ctx, buf, tag[:], iv[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-@(private)
-benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) {
-	fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed 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,
-	)
-}

+ 163 - 0
tests/benchmark/crypto/benchmark_ecc.odin

@@ -0,0 +1,163 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:encoding/hex"
+import "core:testing"
+import "core:text/table"
+import "core:time"
+
+import "core:crypto/ed25519"
+import "core:crypto/x25519"
+import "core:crypto/x448"
+
+@(private = "file")
+ECDH_ITERS :: 10000
+@(private = "file")
+DSA_ITERS :: 10000
+
+@(test)
+benchmark_crypto_ecc :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	bench_ecdh()
+	bench_dsa()
+}
+
+@(private = "file")
+bench_ecdh :: proc() {
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "ECDH")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Scalar-Basepoint", "Scalar-Point")
+
+	append_tbl := proc(tbl: ^table.Table, algo_name: string, bp, sc: time.Duration) {
+		table.aligned_row_of_values(
+			tbl,
+			.Right,
+			algo_name,
+			table.format(tbl, "%8M", bp),
+			table.format(tbl, "%8M", sc),
+		)
+	}
+
+	scalar_bp, scalar := bench_x25519()
+	append_tbl(&tbl, "X25519", scalar_bp, scalar)
+
+	scalar_bp, scalar = bench_x448()
+	append_tbl(&tbl, "X448", scalar_bp, scalar)
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+bench_x25519 :: proc() -> (bp, sc: time.Duration) {
+	point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+	scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
+
+	point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
+	scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
+	out: [x25519.POINT_SIZE]byte = ---
+
+	start := time.tick_now()
+	for _ in 0 ..< ECDH_ITERS {
+		x25519.scalarmult_basepoint(out[:], scalar[:])
+	}
+	bp = time.tick_since(start) / ECDH_ITERS
+
+	start = time.tick_now()
+	for _ in 0 ..< ECDH_ITERS {
+		x25519.scalarmult(out[:], scalar[:], point[:])
+	}
+	sc = time.tick_since(start) / ECDH_ITERS
+
+	return
+}
+
+@(private = "file")
+bench_x448 :: proc() -> (bp, sc: time.Duration) {
+	point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+	scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
+
+	point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
+	scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
+	out: [x448.POINT_SIZE]byte = ---
+
+	start := time.tick_now()
+	for _ in 0 ..< ECDH_ITERS {
+		x448.scalarmult_basepoint(out[:], scalar[:])
+	}
+	bp = time.tick_since(start) / ECDH_ITERS
+
+	start = time.tick_now()
+	for _ in 0 ..< ECDH_ITERS {
+		x448.scalarmult(out[:], scalar[:], point[:])
+	}
+	sc = time.tick_since(start) / ECDH_ITERS
+
+	return
+}
+
+@(private = "file")
+bench_dsa :: proc() {
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "ECDSA/EdDSA")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Op", "Time")
+
+	append_tbl := proc(tbl: ^table.Table, algo_name, op: string, t: time.Duration) {
+		table.aligned_row_of_values(
+			tbl,
+			.Right,
+			algo_name,
+			op,
+			table.format(tbl, "%8M", t),
+		)
+	}
+
+	sk, sig, verif := bench_ed25519()
+	append_tbl(&tbl, "ed25519", "private_key_set_bytes", sk)
+	append_tbl(&tbl, "ed25519", "sign", sig)
+	append_tbl(&tbl, "ed25519", "verify", verif)
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+bench_ed25519 :: proc() -> (sk, sig, verif: time.Duration) {
+	priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
+	priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator)
+	priv_key: ed25519.Private_Key
+	start := time.tick_now()
+	for _ in  0 ..< DSA_ITERS {
+		ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes)
+		assert(ok, "private key should deserialize")
+	}
+	sk = time.tick_since(start) / DSA_ITERS
+
+	pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing"
+	pub_key: ed25519.Public_Key
+	ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:])
+	assert(ok, "public key should deserialize")
+
+	msg := "Got a job for you, 621."
+	sig_bytes: [ed25519.SIGNATURE_SIZE]byte
+	msg_bytes := transmute([]byte)(msg)
+	start = time.tick_now()
+	for _ in  0 ..< DSA_ITERS {
+		ed25519.sign(&priv_key, msg_bytes, sig_bytes[:])
+	}
+	sig = time.tick_since(start) / DSA_ITERS
+
+	start = time.tick_now()
+	for _ in  0 ..< DSA_ITERS {
+		ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes[:])
+		assert(ok, "signature should validate")
+	}
+	verif = time.tick_since(start) / DSA_ITERS
+
+	return
+}

+ 101 - 0
tests/benchmark/crypto/benchmark_hash.odin

@@ -0,0 +1,101 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:testing"
+import "core:text/table"
+import "core:time"
+
+import "core:crypto/hash"
+
+@(private = "file")
+ITERS :: 10000
+@(private = "file")
+SIZES := []int{64, 1024, 65536}
+
+@(test)
+benchmark_crypto_hash :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "Hash")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Size", "Time", "Throughput")
+
+	for algo, i in hash.Algorithm {
+		// Skip the sentinel value, and uncommon algorithms
+		#partial switch algo {
+		case .Invalid:
+			continue
+		case .Legacy_KECCAK_224, .Legacy_KECCAK_256, .Legacy_KECCAK_384, .Legacy_KECCAK_512:
+			// Skip: Legacy and not worth using over SHA3
+			continue
+		case .Insecure_MD5, .Insecure_SHA1:
+			// Skip: Legacy and not worth using at all
+			continue
+		case .SHA224, .SHA384, .SHA3_224, .SHA3_384:
+			// Skip: Uncommon SHA2/SHA3 variants
+			continue
+		case .SM3:
+			// Skip: Liberty Prime is online.  All systems nominal.
+			// Weapons hot.  Mission: the destruction of any and
+			// all Chinese communists.
+			continue
+		}
+		if i > 1 {
+			table.row(&tbl)
+		}
+
+		algo_name := hash.ALGORITHM_NAMES[algo]
+
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				setup = setup_sized_buf,
+				bench = do_bench_hash,
+				teardown = teardown_sized_buf,
+			}
+			tmp := algo
+			context.user_ptr = &tmp
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				algo_name,
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+do_bench_hash :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	digest_: [hash.MAX_DIGEST_SIZE]byte
+
+	buf := options.input
+	algo := (^hash.Algorithm)(context.user_ptr)^
+	digest := digest_[:hash.DIGEST_SIZES[algo]]
+
+	for _ in 0 ..= options.rounds {
+		hash.hash_bytes_to_buffer(algo, buf, digest)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * (options.bytes)
+
+	return
+}

+ 191 - 0
tests/benchmark/crypto/benchmark_mac.odin

@@ -0,0 +1,191 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:testing"
+import "core:text/table"
+import "core:time"
+
+import "core:crypto/hmac"
+import "core:crypto/kmac"
+import "core:crypto/poly1305"
+
+@(private = "file")
+ITERS :: 10000
+@(private = "file")
+SIZES := []int{64, 1024, 65536}
+@(private = "file")
+KMAC_KEY_SIZES := []int{128, 256}
+
+@(test)
+benchmark_crypto_mac :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "MAC")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Size", "Time", "Throughput")
+
+	{
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				setup = setup_sized_buf,
+				bench = do_bench_hmac_sha_256,
+				teardown = teardown_sized_buf,
+			}
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				"HMAC-SHA256",
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	table.row(&tbl)
+
+	for key_sz, i in KMAC_KEY_SIZES {
+		if i > 0 {
+			table.row(&tbl)
+		}
+
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				processed = key_sz, // Pls ignore.
+				setup = setup_sized_buf,
+				bench = do_bench_kmac,
+				teardown = teardown_sized_buf,
+			}
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				table.format(&tbl, "KMAC%d", key_sz),
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	table.row(&tbl)
+
+	{
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				setup = setup_sized_buf,
+				bench = do_bench_poly1305,
+				teardown = teardown_sized_buf,
+			}
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				"poly1305",
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+do_bench_hmac_sha_256 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [32]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+
+	tag: [32]byte = ---
+	for _ in 0 ..= options.rounds {
+		hmac.sum(.SHA256, tag[:], buf, key[:])
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+
+	return
+}
+
+@(private = "file")
+do_bench_kmac :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [kmac.MIN_KEY_SIZE_256]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+	sec_strength := options.processed
+
+	tag: [32]byte = ---
+	for _ in 0 ..= options.rounds {
+		kmac.sum(sec_strength, tag[:sec_strength/8], buf, key[:], nil)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+
+	return
+}
+
+@(private = "file")
+do_bench_poly1305 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [poly1305.KEY_SIZE]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+
+	tag: [poly1305.TAG_SIZE]byte = ---
+	for _ in 0 ..= options.rounds {
+		poly1305.sum(tag[:], buf, key[:])
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+
+	return
+}

+ 145 - 0
tests/benchmark/crypto/benchmark_stream.odin

@@ -0,0 +1,145 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:crypto"
+import "core:testing"
+import "core:text/table"
+import "core:time"
+
+import "core:crypto/aes"
+import "core:crypto/chacha20"
+
+@(private = "file")
+ITERS :: 10000
+@(private = "file")
+SIZES := []int{64, 1024, 65536}
+@(private = "file")
+AES_CTR_KEY_SIZES := []int{128, 192, 256}
+
+@(test)
+benchmark_crypto_stream :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	tbl: table.Table
+	table.init(&tbl)
+	defer table.destroy(&tbl)
+
+	table.caption(&tbl, "Stream Cipher")
+	table.aligned_header_of_values(&tbl, .Right, "Algorithm", "Size", "Time", "Throughput")
+
+	for key_sz, i in AES_CTR_KEY_SIZES {
+		if i > 0 {
+			table.row(&tbl)
+		}
+
+		key := make([]byte, key_sz/8, context.temp_allocator)
+		iv := make([]byte, aes.CTR_IV_SIZE, context.temp_allocator)
+		crypto.rand_bytes(key)
+		crypto.rand_bytes(iv)
+
+		ctx: aes.Context_CTR
+		aes.init_ctr(&ctx, key, iv)
+
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				setup = setup_sized_buf,
+				bench = do_bench_aes_ctr,
+				teardown = teardown_sized_buf,
+			}
+			context.user_ptr = &ctx
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				table.format(&tbl, "AES%d-CTR", key_sz),
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	table.row(&tbl)
+
+	{
+		key := make([]byte, chacha20.KEY_SIZE, context.temp_allocator)
+		iv := make([]byte, chacha20.IV_SIZE, context.temp_allocator)
+		crypto.rand_bytes(key)
+		crypto.rand_bytes(iv)
+
+		ctx: chacha20.Context
+		chacha20.init(&ctx, key, iv)
+
+		for sz, _ in SIZES {
+			options := &time.Benchmark_Options{
+				rounds = ITERS,
+				bytes = sz,
+				setup = setup_sized_buf,
+				bench = do_bench_chacha20,
+				teardown = teardown_sized_buf,
+			}
+			context.user_ptr = &ctx
+
+			err := time.benchmark(options, context.allocator)
+			testing.expect(t, err == nil)
+
+			time_per_iter := options.duration / ITERS
+			table.aligned_row_of_values(
+				&tbl,
+				.Right,
+				"chacha20",
+				table.format(&tbl, "%d", sz),
+				table.format(&tbl, "%8M", time_per_iter),
+				table.format(&tbl, "%5.3f MiB/s", options.megabytes_per_second),
+			)
+		}
+	}
+
+	log_table(&tbl)
+}
+
+@(private = "file")
+do_bench_aes_ctr :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	ctx := (^aes.Context_CTR)(context.user_ptr)
+
+	buf := options.input
+
+	for _ in 0 ..= options.rounds {
+		aes.xor_bytes_ctr(ctx, buf, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+
+	return
+}
+
+@(private = "file")
+do_bench_chacha20 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	ctx := (^chacha20.Context)(context.user_ptr)
+
+	buf := options.input
+
+	for _ in 0 ..= options.rounds {
+		chacha20.xor_bytes(ctx, buf, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+
+	return
+}

+ 50 - 0
tests/benchmark/crypto/benchmark_utils.odin

@@ -0,0 +1,50 @@
+package benchmark_core_crypto
+
+import "core:crypto"
+import "core:fmt"
+import "core:log"
+import "core:strings"
+import "core:text/table"
+import "core:time"
+
+@(private)
+log_table :: #force_inline proc(tbl: ^table.Table) {
+	sb := strings.builder_make()
+	defer strings.builder_destroy(&sb)
+
+	wr := strings.to_writer(&sb)
+
+	fmt.sbprintln(&sb)
+	table.write_plain_table(wr, tbl)
+
+	log.info(strings.to_string(sb))
+}
+
+@(private)
+setup_sized_buf :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	assert(options != nil)
+
+	options.input = make([]u8, options.bytes, allocator)
+	if len(options.input) > 0 {
+		crypto.rand_bytes(options.input)
+	}
+	return nil if len(options.input) == options.bytes else .Allocation_Error
+}
+
+@(private)
+teardown_sized_buf :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	assert(options != nil)
+
+	delete(options.input)
+	return nil
+}