Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit 0b906a2

Browse files
committed
core.internal.hash prefer FNV to MurmurHash3 for short sequences of statically known length
1 parent d37c37d commit 0b906a2

File tree

1 file changed

+86
-10
lines changed

1 file changed

+86
-10
lines changed

src/core/internal/hash.d

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ if (!is(T == enum) && __traits(isStaticArray, T) && canBitwiseHash!T)
257257
//else static if (T.length == 1)
258258
// return hashOf(val[0], seed);
259259
//else
260-
// /+ hash like a dynamic array +/
260+
// return bytesHashWithExactSizeAndAlignment!T(toUbyte(val), seed);
261261
//
262262
// ... but that's inefficient when using a runtime TypeInfo (introduces a branch)
263263
// and PR #2243 wants typeid(T).getHash(&val) to produce the same result as
@@ -412,7 +412,7 @@ size_t hashOf(T)(scope const T val, size_t seed = 0) if (!is(T == enum) && __tra
412412
else static if (T.mant_dig == double.mant_dig && T.sizeof == ulong.sizeof)
413413
return hashOf(*cast(const ulong*) &data, seed);
414414
else
415-
return bytesHashAlignedBy!T(toUbyte(data), seed);
415+
return bytesHashWithExactSizeAndAlignment!T(toUbyte(data), seed);
416416
}
417417
else
418418
{
@@ -526,25 +526,76 @@ q{
526526
}
527527
else static if ((is(T == struct) && !canBitwiseHash!T) || T.tupleof.length == 1)
528528
{
529+
static if (isChained) size_t h = seed;
529530
static foreach (i, F; typeof(val.tupleof))
530531
{
531-
static if (i != 0)
532-
h = hashOf(val.tupleof[i], h);
533-
else static if (isChained)
534-
size_t h = hashOf(val.tupleof[i], seed);
532+
static if (__traits(isStaticArray, F))
533+
{
534+
static if (i == 0 && !isChained) size_t h = 0;
535+
static if (F.sizeof > 0 && canBitwiseHash!F)
536+
// May use smallBytesHash instead of bytesHash.
537+
h = bytesHashWithExactSizeAndAlignment!F(toUbyte(val.tupleof[i]), h);
538+
else
539+
// We can avoid the "double hashing" the top-level version uses
540+
// for consistency with TypeInfo.getHash.
541+
foreach (ref e; val.tupleof[i])
542+
h = hashOf(e, h);
543+
}
544+
else static if (is(F == struct) || is(F == union))
545+
{
546+
static if (hasCallableToHash!F)
547+
{
548+
static if (i == 0 && !isChained)
549+
size_t h = val.tupleof[i].toHash();
550+
else
551+
h = hashOf(cast(size_t) val.tupleof[i].toHash(), h);
552+
}
553+
else static if (F.tupleof.length == 1)
554+
{
555+
// Handle the single member case separately to avoid unnecessarily using bytesHash.
556+
static if (i == 0 && !isChained)
557+
size_t h = hashOf(val.tupleof[i].tupleof[0]);
558+
else
559+
h = hashOf(val.tupleof[i].tupleof[0], h);
560+
}
561+
else static if (canBitwiseHash!F)
562+
{
563+
// May use smallBytesHash instead of bytesHash.
564+
static if (i == 0 && !isChained) size_t h = 0;
565+
h = bytesHashWithExactSizeAndAlignment!F(toUbyte(val.tupleof[i]), h);
566+
}
567+
else
568+
{
569+
// Nothing special happening.
570+
static if (i == 0 && !isChained)
571+
size_t h = hashOf(val.tupleof[i]);
572+
else
573+
h = hashOf(val.tupleof[i], h);
574+
}
575+
}
535576
else
536-
size_t h = hashOf(val.tupleof[i]);
577+
{
578+
// Nothing special happening.
579+
static if (i == 0 && !isChained)
580+
size_t h = hashOf(val.tupleof[i]);
581+
else
582+
h = hashOf(val.tupleof[i], h);
583+
}
537584
}
538585
return h;
539586
}
540587
else static if (is(typeof(toUbyte(val)) == const(ubyte)[]))//CTFE ready for structs without reference fields
541588
{
589+
// Not using bytesHashWithExactSizeAndAlignment here because
590+
// the result may differ from typeid(T).hashOf(&val).
542591
return bytesHashAlignedBy!T(toUbyte(val), seed);
543592
}
544593
else // CTFE unsupported
545594
{
546-
assert(!__ctfe, "unable to compute hash of "~T.stringof);
595+
assert(!__ctfe, "unable to compute hash of "~T.stringof~" at compile time");
547596
const(ubyte)[] bytes = (() @trusted => (cast(const(ubyte)*)&val)[0 .. T.sizeof])();
597+
// Not using bytesHashWithExactSizeAndAlignment here because
598+
// the result may differ from typeid(T).hashOf(&val).
548599
return bytesHashAlignedBy!T(bytes, seed);
549600
}
550601
}
@@ -578,9 +629,9 @@ if (!is(T == enum) && (is(T == struct) || is(T == union))
578629
@trusted @nogc nothrow pure
579630
size_t hashOf(T)(scope const T val, size_t seed = 0) if (!is(T == enum) && is(T == delegate))
580631
{
581-
assert(!__ctfe, "unable to compute hash of "~T.stringof);
632+
assert(!__ctfe, "unable to compute hash of "~T.stringof~" at compile time");
582633
const(ubyte)[] bytes = (cast(const(ubyte)*)&val)[0 .. T.sizeof];
583-
return bytesHashAlignedBy!T(bytes, seed);
634+
return bytesHashWithExactSizeAndAlignment!T(bytes, seed);
584635
}
585636

586637
//address-based class hash. CTFE only if null.
@@ -667,6 +718,31 @@ private template bytesHashAlignedBy(AlignType)
667718
alias bytesHashAlignedBy = bytesHash!(AlignType.alignof >= uint.alignof);
668719
}
669720

721+
private template bytesHashWithExactSizeAndAlignment(SizeAndAlignType)
722+
{
723+
static if (SizeAndAlignType.alignof < uint.alignof
724+
? SizeAndAlignType.sizeof <= 12
725+
: SizeAndAlignType.sizeof <= 10)
726+
alias bytesHashWithExactSizeAndAlignment = smallBytesHash;
727+
else
728+
alias bytesHashWithExactSizeAndAlignment = bytesHashAlignedBy!SizeAndAlignType;
729+
}
730+
731+
// Fowler/Noll/Vo hash. http://www.isthe.com/chongo/tech/comp/fnv/
732+
private size_t fnv()(scope const(ubyte)[] bytes, size_t seed) @nogc nothrow pure @safe
733+
{
734+
static if (size_t.max <= uint.max)
735+
enum prime = (1U << 24) + (1U << 8) + 0x93U;
736+
else static if (size_t.max <= ulong.max)
737+
enum prime = (1UL << 40) + (1UL << 8) + 0xb3UL;
738+
else
739+
enum prime = (size_t(1) << 88) + (size_t(1) << 8) + size_t(0x3b);
740+
foreach (b; bytes)
741+
seed = (seed ^ b) * prime;
742+
return seed;
743+
}
744+
private alias smallBytesHash = fnv;
745+
670746
//-----------------------------------------------------------------------------
671747
// Block read - if your platform needs to do endian-swapping or can only
672748
// handle aligned reads, do the conversion here

0 commit comments

Comments
 (0)