aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2021-09-13 20:58:26 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2021-09-13 20:58:26 +0200
commita641ef95c0f47cc172d6e3ca8ef8934a73f50081 (patch)
tree24c909809c516a8b31e7d589399146c51f8486b3
parente9b9d15de7bc08bdf2891586589e215832c9a3cc (diff)
Add XXH3-64 + tests.
-rw-r--r--core/hash/xxhash/xxhash_3.odin268
-rw-r--r--tests/core/hash/test_core_hash.odin28
2 files changed, 170 insertions, 126 deletions
diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin
index 415ed928d..b43a72005 100644
--- a/core/hash/xxhash/xxhash_3.odin
+++ b/core/hash/xxhash/xxhash_3.odin
@@ -11,22 +11,24 @@ package xxhash
import "core:intrinsics"
-/* *********************************************************************
-* XXH3
-* New generation hash designed for speed on small keys and vectorization
-************************************************************************
+/*
+*************************************************************************
+* XXH3
+* New generation hash designed for speed on small keys and vectorization
+*************************************************************************
* One goal of XXH3 is to make it fast on both 32-bit and 64-bit, while
* remaining a true 64-bit/128-bit hash function.
+* ==========================================
+* XXH3 default settings
+* ==========================================
*/
-/* ==========================================
- * XXH3 default settings
- * ========================================== */
-
-XXH_ACC_ALIGN :: 8 /* scalar */
-
-XXH3_SECRET_SIZE_MIN :: 136
+/*
+ Custom secrets have a default length of 192, but can be set to a different size.
+ The minimum secret size is 136 bytes. It must also be a multiple of 64.
+*/
XXH_SECRET_DEFAULT_SIZE :: max(XXH3_SECRET_SIZE_MIN, #config(XXH_SECRET_DEFAULT_SIZE, 192))
+#assert(XXH_SECRET_DEFAULT_SIZE % 64 == 0)
XXH3_kSecret :: [?]u8{
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
@@ -42,8 +44,42 @@ XXH3_kSecret :: [?]u8{
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
}
-#assert(size_of(XXH3_kSecret) == 192)
+/*
+ Do not change this constant.
+*/
+XXH3_SECRET_SIZE_MIN :: 136
+#assert(size_of(XXH3_kSecret) == 192 && size_of(XXH3_kSecret) > XXH3_SECRET_SIZE_MIN)
+XXH_ACC_ALIGN :: 8 /* scalar */
+
+/*
+ This is the optimal update size for incremental hashing.
+*/
+XXH3_INTERNAL_BUFFER_SIZE :: 256
+
+/*
+ Streaming state.
+
+ IMPORTANT: This structure has a strict alignment requirement of 64 bytes!! **
+ Do not allocate this with `make()` or `new`, it will not be sufficiently aligned.
+ Use`XXH3_create_state` and `XXH3_destroy_state, or stack allocation.
+*/
+XXH3_state :: struct {
+ acc: [8]u64,
+ custom_secret: [XXH_SECRET_DEFAULT_SIZE]u8,
+ buffer: [XXH3_INTERNAL_BUFFER_SIZE]u8,
+ buffered_size: u32,
+ reserved32: u32,
+ stripes_so_far: uint,
+ total_len: u64,
+ stripes_per_block: uint,
+ secret_limit: uint,
+ seed: u64,
+ reserved64: u64,
+ external_secret: ^[]u8,
+}
+#assert(offset_of(XXH3_state, acc) % 64 == 0 && offset_of(XXH3_state, custom_secret) % 64 == 0 &&
+ offset_of(XXH3_state, buffer) % 64 == 0)
/************************************************************************
* XXH3 128-bit variant
@@ -54,7 +90,6 @@ XXH3_kSecret :: [?]u8{
*/
xxh_u128 :: u128
XXH3_128_hash :: u128
-XXH3_128_DEFAULT_SEED :: xxh_u64(0)
XXH128_hash_t :: struct #raw_union {
using raw: struct {
@@ -436,7 +471,12 @@ XXH3_128bits_internal :: #force_inline proc(
/* === Public XXH128 API === */
-XXH3_128_with_seed :: proc(input: []u8, seed := XXH3_128_DEFAULT_SEED) -> (hash: XXH3_128_hash) {
+XXH3_128_default :: proc(input: []u8) -> (hash: XXH3_128_hash) {
+ k := XXH3_kSecret
+ return XXH3_128bits_internal(input, 0, k[:], XXH3_hashLong_128b_withSeed)
+}
+
+XXH3_128_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: XXH3_128_hash) {
k := XXH3_kSecret
return XXH3_128bits_internal(input, seed, k[:], XXH3_hashLong_128b_withSeed)
}
@@ -444,18 +484,17 @@ XXH3_128_with_seed :: proc(input: []u8, seed := XXH3_128_DEFAULT_SEED) -> (hash:
XXH3_128_with_secret :: proc(input: []u8, secret: []u8) -> (hash: XXH3_128_hash) {
return XXH3_128bits_internal(input, 0, secret, XXH3_hashLong_128b_withSecret)
}
-XXH3_128 :: proc { XXH3_128_with_seed, XXH3_128_with_secret }
-
-/*
+XXH3_128 :: proc { XXH3_128_default, XXH3_128_with_seed, XXH3_128_with_secret }
/* === XXH3 128-bit streaming === */
/*
- * All the functions are actually the same as for 64-bit streaming variant.
- * The only difference is the finalization routine.
- */
+ All the functions are actually the same as for 64-bit streaming variant.
+ The only difference is the finalization routine.
+*/
+
+/*
-/*! @ingroup xxh3_family */
XXH_PUBLIC_API XXH_errorcode
XXH3_128bits_reset(XXH3_state_t* statePtr)
{
@@ -635,7 +674,8 @@ XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64
assert(secret != nil)
seed := seed
- seed ~= u64(byte_swap(u32(seed) << 32))
+ seed ~= (u64(byte_swap(u32(seed))) << 32)
+
#no_bounds_check {
input1 := XXH32_read32(input)
input2 := XXH32_read32(input[length - 4:])
@@ -907,7 +947,6 @@ XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, s
/* last partial block */
#no_bounds_check {
stripes := ((length - 1) - (block_len * blocks)) / XXH_STRIPE_LEN
-
XXH3_accumulate(acc, input[blocks * block_len:], secret, stripes, f_acc512)
/* last stripe */
@@ -935,142 +974,119 @@ XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u6
return XXH3_avalanche(result64)
}
+@(optimization_mode="speed")
+XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8,
+ f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) -> (hash: xxh_u64) {
-/*
-XXH_FORCE_INLINE XXH64_hash_t
-XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len,
- const void* XXH_RESTRICT secret, size_t secretSize,
- XXH3_f_accumulate_512 f_acc512,
- XXH3_f_scrambleAcc f_scramble)
-{
- XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
+ acc: [XXH_ACC_NB]xxh_u64 = XXH3_INIT_ACC
- XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc512, f_scramble);
+ XXH3_hashLong_internal_loop(acc[:], input, secret, f_acc512, f_scramble)
/* converge into final hash */
- XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ #assert(size_of(acc) == 64)
/* do not align on 8, so that the secret is different from the accumulator */
-#define XXH_SECRET_MERGEACCS_START 11
- XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
- return XXH3_mergeAccs(acc, (const xxh_u8*)secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * XXH_PRIME64_1);
+ XXH_SECRET_MERGEACCS_START :: 11
+ assert(len(secret) >= size_of(acc) + XXH_SECRET_MERGEACCS_START)
+ return XXH3_mergeAccs(acc[:], secret[XXH_SECRET_MERGEACCS_START:], xxh_u64(len(input)) * XXH_PRIME64_1)
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
- */
-XXH_NO_INLINE XXH64_hash_t
-XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
- XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
-{
- (void)seed64;
- return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate_512, XXH3_scrambleAcc);
+ It's important for performance that XXH3_hashLong is not inlined.
+*/
+XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
+ return XXH3_hashLong_64b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator)
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
- * Since the function is not inlined, the compiler may not be able to understand that,
- * in some scenarios, its `secret` argument is actually a compile time constant.
- * This variant enforces that the compiler can detect that,
- * and uses this opportunity to streamline the generated code for better performance.
- */
-XXH_NO_INLINE XXH64_hash_t
-XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
- XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
-{
- (void)seed64; (void)secret; (void)secretLen;
- return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate_512, XXH3_scrambleAcc);
+ It's important for performance that XXH3_hashLong is not inlined.
+ Since the function is not inlined, the compiler may not be able to understand that,
+ in some scenarios, its `secret` argument is actually a compile time constant.
+ This variant enforces that the compiler can detect that,
+ and uses this opportunity to streamline the generated code for better performance.
+*/
+XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
+ k := XXH3_kSecret
+ return XXH3_hashLong_64b_internal(input, k[:], XXH3_accumulate_512, XXH3_scramble_accumulator)
}
/*
- * XXH3_hashLong_64b_withSeed():
- * Generate a custom key based on alteration of default XXH3_kSecret with the seed,
- * and then use this key for long mode hashing.
- *
- * This operation is decently fast but nonetheless costs a little bit of time.
- * Try to avoid it whenever possible (typically when seed==0).
- *
- * It's important for performance that XXH3_hashLong is not inlined. Not sure
- * why (uop cache maybe?), but the difference is large and easily measurable.
- */
-XXH_FORCE_INLINE XXH64_hash_t
-XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len,
- XXH64_hash_t seed,
- XXH3_f_accumulate_512 f_acc512,
- XXH3_f_scrambleAcc f_scramble,
- XXH3_f_initCustomSecret f_initSec)
-{
- if (seed == 0)
- return XXH3_hashLong_64b_internal(input, len,
- XXH3_kSecret, sizeof(XXH3_kSecret),
- f_acc512, f_scramble);
- { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
- f_initSec(secret, seed);
- return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret),
- f_acc512, f_scramble);
+ XXH3_hashLong_64b_withSeed():
+ Generate a custom key based on alteration of default XXH3_kSecret with the seed,
+ and then use this key for long mode hashing.
+
+ This operation is decently fast but nonetheless costs a little bit of time.
+ Try to avoid it whenever possible (typically when seed==0).
+
+ It's important for performance that XXH3_hashLong is not inlined. Not sure
+ why (uop cache maybe?), but the difference is large and easily measurable.
+*/
+XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc(input: []u8,
+ seed: xxh_u64,
+ f_acc512: XXH3_accumulate_512_f,
+ f_scramble: XXH3_scramble_accumulator_f,
+ f_init_sec: XXH3_init_custom_secret_f) -> (hash: xxh_u64) {
+ if seed == 0 {
+ k := XXH3_kSecret
+ return XXH3_hashLong_64b_internal(input, k[:], f_acc512, f_scramble)
+ }
+ {
+ secret: [XXH_SECRET_DEFAULT_SIZE]u8
+ f_init_sec(secret[:], seed)
+ return XXH3_hashLong_64b_internal(input, secret[:], f_acc512, f_scramble)
}
}
/*
- * It's important for performance that XXH3_hashLong is not inlined.
- */
-XXH_NO_INLINE XXH64_hash_t
-XXH3_hashLong_64b_withSeed(const void* input, size_t len,
- XXH64_hash_t seed, const xxh_u8* secret, size_t secretLen)
-{
- (void)secret; (void)secretLen;
- return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
- XXH3_accumulate_512, XXH3_scrambleAcc, XXH3_initCustomSecret);
+ It's important for performance that XXH3_hashLong is not inlined.
+*/
+XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
+ return XXH3_hashLong_64b_withSeed_internal(input, seed, XXH3_accumulate_512, XXH3_scramble_accumulator, XXH3_init_custom_secret)
}
-typedef XXH64_hash_t (*XXH3_hashLong64_f)(const void* XXH_RESTRICT, size_t,
- XXH64_hash_t, const xxh_u8* XXH_RESTRICT, size_t);
+XXH3_hashLong64_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: xxh_u64)
-XXH_FORCE_INLINE XXH64_hash_t
-XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len,
- XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen,
- XXH3_hashLong64_f f_hashLong)
-{
- XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
+XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) {
+
+
+
+
+ assert(len(secret) >= XXH3_SECRET_SIZE_MIN)
/*
- * If an action is to be taken if `secretLen` condition is not respected,
- * it should be done here.
- * For now, it's a contract pre-condition.
- * Adding a check and a branch here would cost performance at every hash.
- * Also, note that function signature doesn't offer room to return an error.
- */
- if (len <= 16)
- return XXH3_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64);
- if (len <= 128)
- return XXH3_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
- if (len <= XXH3_MIDSIZE_MAX)
- return XXH3_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
- return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen);
+ If an action is to be taken if len(secret) condition is not respected, it should be done here.
+ For now, it's a contract pre-condition.
+ Adding a check and a branch here would cost performance at every hash.
+ Also, note that function signature doesn't offer room to return an error.
+ */
+ length := len(input)
+ switch {
+ case length <= 16: return XXH3_len_0to16_64b(input, secret, seed)
+ case length <= 128: return XXH3_len_17to128_64b(input, secret, seed)
+ case length <= XXH3_MIDSIZE_MAX: return XXH3_len_129to240_64b(input, secret, seed)
+ case: return f_hashLong(input, seed, secret)
+ }
+ unreachable()
}
-
/* === Public entry point === */
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(const void* input, size_t len)
-{
- return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
+XXH3_64_default :: proc(input: []u8) -> (hash: xxh_u64) {
+ k := XXH3_kSecret
+ return XXH3_64bits_internal(input, 0, k[:], XXH3_hashLong_64b_default)
}
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSecret(const void* input, size_t len, const void* secret, size_t secretSize)
-{
- return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
+XXH3_64_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: xxh_u64) {
+ k := XXH3_kSecret
+ return XXH3_64bits_internal(input, seed, k[:], XXH3_hashLong_64b_withSeed)
}
-/*! @ingroup xxh3_family */
-XXH_PUBLIC_API XXH64_hash_t
-XXH3_64bits_withSeed(const void* input, size_t len, XXH64_hash_t seed)
-{
- return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
+XXH3_64_with_secret :: proc(input, secret: []u8) -> (hash: xxh_u64) {
+ return XXH3_64bits_internal(input, 0, secret, XXH3_hashLong_64b_withSecret)
}
+XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret }
+
+/*
/* === XXH3 streaming === */
diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin
index 0189ccfe3..74884dd68 100644
--- a/tests/core/hash/test_core_hash.odin
+++ b/tests/core/hash/test_core_hash.odin
@@ -79,6 +79,19 @@ benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.a
return nil
}
+benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+ buf := options.input
+
+ h: u64
+ for _ in 0..=options.rounds {
+ h = xxhash.XXH3_64(buf)
+ }
+ options.count = options.rounds
+ options.processed = options.rounds * options.bytes
+ options.hash = u128(h)
+ return nil
+}
+
benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
buf := options.input
@@ -143,6 +156,21 @@ test_benchmark_runner :: proc(t: ^testing.T) {
expect(t, options.hash == 0x87d2a1b6e1163ef1, name)
benchmark_print(name, options)
+ name = "XXH3_64 100 zero bytes"
+ options.bytes = 100
+ options.bench = benchmark_xxh3_64
+ err = time.benchmark(options, context.allocator)
+ expect(t, err == nil, name)
+ expect(t, options.hash == 0x801fedc74ccd608c, name)
+ benchmark_print(name, options)
+
+ name = "XXH3_64 1 MiB zero bytes"
+ options.bytes = 1_048_576
+ err = time.benchmark(options, context.allocator)
+ expect(t, err == nil, name)
+ expect(t, options.hash == 0x918780b90550bf34, name)
+ benchmark_print(name, options)
+
name = "XXH3_128 100 zero bytes"
options.bytes = 100
options.bench = benchmark_xxh3_128