optimization: migrate SipHashUint256 to PresaltedSipHasher

Replaces standalone `SipHashUint256` with an `operator()` overload in `PresaltedSipHasher`.
Updates all hasher classes (`SaltedUint256Hasher`, `SaltedTxidHasher`, `SaltedWtxidHasher`) to use `PresaltedSipHasher` internally, enabling the same constant-state caching optimization while keeping behavior unchanged.

Benchmark was also adjusted to cache the salting part.
This commit is contained in:
Lőrinc
2025-09-30 20:58:19 -04:00
parent ec11b9fede
commit 9ca52a4cbe
8 changed files with 45 additions and 55 deletions

View File

@@ -193,13 +193,11 @@ static void SHA512(benchmark::Bench& bench)
static void SipHash_32b(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
auto k0{rng.rand64()}, k1{rng.rand64()};
PresaltedSipHasher presalted_sip_hasher(rng.rand64(), rng.rand64());
auto val{rng.rand256()};
auto i{0U};
bench.run([&] {
ankerl::nanobench::doNotOptimizeAway(SipHashUint256(k0, k1, val));
++k0;
++k1;
ankerl::nanobench::doNotOptimizeAway(presalted_sip_hasher(val));
++i;
val.data()[i % uint256::size()] ^= i & 0xFF;
});

View File

@@ -42,7 +42,8 @@ void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const {
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const {
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
return SipHashUint256(shorttxidk0, shorttxidk1, wtxid.ToUint256()) & 0xffffffffffffL;
PresaltedSipHasher hasher(shorttxidk0, shorttxidk1); // TODO extract
return hasher(wtxid.ToUint256()) & 0xffffffffffffL;
}
/* Reconstructing a compact block is in the hot-path for block relay,

View File

@@ -96,16 +96,11 @@ uint64_t CSipHasher::Finalize() const
return v0 ^ v1 ^ v2 ^ v3;
}
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
uint64_t PresaltedSipHasher::operator()(const uint256& val) const noexcept
{
/* Specialized implementation for efficiency */
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
uint64_t d = val.GetUint64(0);
// TODO moved in followup commit
uint64_t v0 = CSipHasher::C0 ^ k0;
uint64_t v1 = CSipHasher::C1 ^ k1;
uint64_t v2 = CSipHasher::C2 ^ k0;
uint64_t v3 = CSipHasher::C3 ^ k1 ^ d;
v3 ^= d;
SIPROUND;
SIPROUND;

View File

@@ -37,18 +37,15 @@ public:
uint64_t Finalize() const;
};
/** Optimized SipHash-2-4 implementation for uint256.
/**
* Optimized SipHash-2-4 implementation for uint256.
*
* It is identical to:
* SipHasher(k0, k1)
* .Write(val.GetUint64(0))
* .Write(val.GetUint64(1))
* .Write(val.GetUint64(2))
* .Write(val.GetUint64(3))
* .Finalize()
* This class caches the initial SipHash v[0..3] state derived from (k0, k1)
* and implements a specialized hashing path for uint256 values, with or
* without an extra 32-bit word. The internal state is immutable, so
* PresaltedSipHasher instances can be reused for multiple hashes with the
* same key.
*/
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
class PresaltedSipHasher
{
uint64_t v[4];
@@ -61,6 +58,7 @@ public:
v[3] = CSipHasher::C3 ^ k1;
}
uint64_t operator()(const uint256& val) const noexcept;
uint64_t operator()(const uint256& val, uint32_t extra) const noexcept;
};

View File

@@ -118,7 +118,7 @@ FUZZ_TARGET(integer, .init = initialize_integer)
}
(void)MillisToTimeval(i64);
(void)SighashToStr(uch);
(void)SipHashUint256(u64, u64, u256);
(void)PresaltedSipHasher(u64, u64)(u256);
(void)PresaltedSipHasher(u64, u64)(u256, u32);
(void)ToLower(ch);
(void)ToUpper(ch);

View File

@@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(siphash)
hasher.Write(0x2F2E2D2C2B2A2928ULL);
BOOST_CHECK_EQUAL(hasher.Finalize(), 0xe612a3cb9ecba951ull);
BOOST_CHECK_EQUAL(SipHashUint256(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL, uint256{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}), 0x7127512f72f27cceull);
BOOST_CHECK_EQUAL(PresaltedSipHasher(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL)(uint256{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}), 0x7127512f72f27cceull);
// Check test vectors from spec, one byte at a time
CSipHasher hasher2(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
@@ -128,9 +128,9 @@ BOOST_AUTO_TEST_CASE(siphash)
// and the test would be affected by default tx version bumps if not fixed.
tx.version = 1;
ss << TX_WITH_WITNESS(tx);
BOOST_CHECK_EQUAL(SipHashUint256(1, 2, ss.GetHash()), 0x79751e980c2a0a35ULL);
BOOST_CHECK_EQUAL(PresaltedSipHasher(1, 2)(ss.GetHash()), 0x79751e980c2a0a35ULL);
// Check consistency between CSipHasher and SipHashUint256 and PresaltedSipHasher.
// Check consistency between CSipHasher and PresaltedSipHasher.
FastRandomContext ctx;
for (int i = 0; i < 16; ++i) {
uint64_t k0 = ctx.rand64();
@@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE(siphash)
CSipHasher sip256(k0, k1);
sip256.Write(x);
BOOST_CHECK_EQUAL(SipHashUint256(k0, k1, x), sip256.Finalize()); // TODO modified in follow-up commit
BOOST_CHECK_EQUAL(PresaltedSipHasher(k0, k1)(x), sip256.Finalize());
CSipHasher sip288 = sip256;
uint32_t n = ctx.rand32();

View File

@@ -7,17 +7,20 @@
#include <span.h>
#include <util/hasher.h>
SaltedUint256Hasher::SaltedUint256Hasher() :
k0{FastRandomContext().rand64()},
k1{FastRandomContext().rand64()} {}
SaltedUint256Hasher::SaltedUint256Hasher() : m_hasher{
FastRandomContext().rand64(),
FastRandomContext().rand64()}
{}
SaltedTxidHasher::SaltedTxidHasher() :
k0{FastRandomContext().rand64()},
k1{FastRandomContext().rand64()} {}
SaltedTxidHasher::SaltedTxidHasher() : m_hasher{
FastRandomContext().rand64(),
FastRandomContext().rand64()}
{}
SaltedWtxidHasher::SaltedWtxidHasher() :
k0{FastRandomContext().rand64()},
k1{FastRandomContext().rand64()} {}
SaltedWtxidHasher::SaltedWtxidHasher() : m_hasher{
FastRandomContext().rand64(),
FastRandomContext().rand64()}
{}
SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : m_hasher{
deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64(),

View File

@@ -17,47 +17,43 @@
class SaltedUint256Hasher
{
private:
/** Salt */
const uint64_t k0, k1;
const PresaltedSipHasher m_hasher;
public:
SaltedUint256Hasher();
size_t operator()(const uint256& hash) const {
return SipHashUint256(k0, k1, hash);
size_t operator()(const uint256& hash) const
{
return m_hasher(hash);
}
};
class SaltedTxidHasher
{
private:
/** Salt */
const uint64_t k0, k1;
const PresaltedSipHasher m_hasher;
public:
SaltedTxidHasher();
size_t operator()(const Txid& txid) const {
return SipHashUint256(k0, k1, txid.ToUint256());
size_t operator()(const Txid& txid) const
{
return m_hasher(txid.ToUint256());
}
};
class SaltedWtxidHasher
{
private:
/** Salt */
const uint64_t k0, k1;
const PresaltedSipHasher m_hasher;
public:
SaltedWtxidHasher();
size_t operator()(const Wtxid& wtxid) const {
return SipHashUint256(k0, k1, wtxid.ToUint256());
size_t operator()(const Wtxid& wtxid) const
{
return m_hasher(wtxid.ToUint256());
}
};
class SaltedOutpointHasher
{
const PresaltedSipHasher m_hasher;
@@ -80,8 +76,7 @@ public:
}
};
struct FilterHeaderHasher
{
struct FilterHeaderHasher {
size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); }
};