فهرست منبع

Add XXH3-64 + tests.

Jeroen van Rijn 4 سال پیش
والد
کامیت
a641ef95c0
2فایلهای تغییر یافته به همراه170 افزوده شده و 126 حذف شده
  1. 142 126
      core/hash/xxhash/xxhash_3.odin
  2. 28 0
      tests/core/hash/test_core_hash.odin

+ 142 - 126
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   === */
 

+ 28 - 0
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