Compare commits

...

9 Commits

Author SHA1 Message Date
Bronek Kozicki
e874c4061e Improvements 2025-08-08 11:38:26 +01:00
Bart
892876af5e Merge branch 'develop' into a1q123456/optimise-xxhash 2025-08-07 18:28:36 -04:00
JCW
a2a5a97d70 Merge remote-tracking branch 'origin/develop' into a1q123456/optimise-xxhash 2025-08-05 17:36:38 +01:00
Bronek Kozicki
096ab3a86d Merge branch 'develop' into a1q123456/optimise-xxhash 2025-07-28 10:12:51 +01:00
JCW
64f1f2d580 Fix formatting
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
2025-07-25 16:38:56 +01:00
JCW
3f9b724ed8 Fix build error 2025-07-25 16:27:31 +01:00
Jingchen
cad9eba7ed Merge branch 'develop' into a1q123456/optimise-xxhash 2025-07-25 15:57:19 +01:00
JCW
dbb989921c Remove iota and ranges to support older gcc 2025-06-06 09:23:55 +01:00
JCW
a0cf51d454 Optimize hash performance by avoiding allocating hash state object 2025-06-04 16:21:34 +01:00
2 changed files with 301 additions and 21 deletions

View File

@@ -24,32 +24,111 @@
#include <xxhash.h>
#include <array>
#include <cstddef>
#include <new>
#include <type_traits>
#include <cstdint>
#include <optional>
#include <span>
namespace beast {
class xxhasher
{
private:
// requires 64-bit std::size_t
static_assert(sizeof(std::size_t) == 8, "");
public:
using result_type = std::size_t;
XXH3_state_t* state_;
private:
static_assert(sizeof(std::size_t) == 8, "requires 64-bit std::size_t");
// Have an internal buffer to avoid the streaming API
// A 64-byte buffer should to be big enough for us
static constexpr std::size_t INTERNAL_BUFFER_SIZE = 64;
alignas(64) std::array<std::uint8_t, INTERNAL_BUFFER_SIZE> buffer_;
std::span<std::uint8_t> readBuffer_;
std::span<std::uint8_t> writeBuffer_;
std::optional<XXH64_hash_t> seed_;
XXH3_state_t* state_ = nullptr;
void
resetBuffers()
{
writeBuffer_ = std::span{buffer_};
readBuffer_ = {};
}
void
updateHash(void const* data, std::size_t len)
{
if (writeBuffer_.size() < len)
{
flushToState(data, len);
}
else
{
std::memcpy(writeBuffer_.data(), data, len);
writeBuffer_ = writeBuffer_.subspan(len);
readBuffer_ = std::span{
std::begin(buffer_), buffer_.size() - writeBuffer_.size()};
}
}
static XXH3_state_t*
allocState()
{
auto ret = XXH3_createState();
if (ret == nullptr)
throw std::bad_alloc();
throw std::bad_alloc(); // LCOV_EXCL_LINE
return ret;
}
public:
using result_type = std::size_t;
void
flushToState(void const* data, std::size_t len)
{
if (!state_)
{
state_ = allocState();
if (seed_.has_value())
{
XXH3_64bits_reset_withSeed(state_, *seed_);
}
else
{
XXH3_64bits_reset(state_);
}
}
XXH3_64bits_update(state_, readBuffer_.data(), readBuffer_.size());
resetBuffers();
if (data && len)
{
XXH3_64bits_update(state_, data, len);
}
}
result_type
retrieveHash()
{
if (state_)
{
flushToState(nullptr, 0);
return XXH3_64bits_digest(state_);
}
else
{
if (seed_.has_value())
{
return XXH3_64bits_withSeed(
readBuffer_.data(), readBuffer_.size(), *seed_);
}
else
{
return XXH3_64bits(readBuffer_.data(), readBuffer_.size());
}
}
}
public:
static constexpr auto const endian = boost::endian::order::native;
xxhasher(xxhasher const&) = delete;
@@ -58,43 +137,43 @@ public:
xxhasher()
{
state_ = allocState();
XXH3_64bits_reset(state_);
resetBuffers();
}
~xxhasher() noexcept
{
XXH3_freeState(state_);
if (state_)
{
XXH3_freeState(state_);
}
}
template <
class Seed,
std::enable_if_t<std::is_unsigned<Seed>::value>* = nullptr>
explicit xxhasher(Seed seed)
explicit xxhasher(Seed seed) : seed_(seed)
{
state_ = allocState();
XXH3_64bits_reset_withSeed(state_, seed);
resetBuffers();
}
template <
class Seed,
std::enable_if_t<std::is_unsigned<Seed>::value>* = nullptr>
xxhasher(Seed seed, Seed)
xxhasher(Seed seed, Seed) : seed_(seed)
{
state_ = allocState();
XXH3_64bits_reset_withSeed(state_, seed);
resetBuffers();
}
void
operator()(void const* key, std::size_t len) noexcept
{
XXH3_64bits_update(state_, key, len);
updateHash(key, len);
}
explicit
operator std::size_t() noexcept
operator result_type() noexcept
{
return XXH3_64bits_digest(state_);
return retrieveHash();
}
};

View File

@@ -0,0 +1,201 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/beast/hash/xxhasher.h>
#include <xrpl/beast/unit_test.h>
namespace beast {
class XXHasher_test : public unit_test::suite
{
public:
void
testWithoutSeed()
{
testcase("Without Seed");
xxhasher hasher{};
std::string objectToHash{"Hello, xxHash!"};
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
16042857369214894119ULL);
}
void
testWithSeed()
{
testcase("With Seed");
xxhasher hasher{static_cast<std::uint32_t>(102)};
std::string objectToHash{"Hello, xxHash!"};
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
14440132435660934800ULL);
}
void
testWithTwoSeeds()
{
testcase("With Two Seeds");
xxhasher hasher{
static_cast<std::uint32_t>(102), static_cast<std::uint32_t>(103)};
std::string objectToHash{"Hello, xxHash!"};
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
14440132435660934800ULL);
}
void
testBigObjectWithMultiupleSmallUpdatesWithoutSeed()
{
testcase("Big Object With Multiuple Small Updates Without Seed");
xxhasher hasher{};
std::string objectToHash{"Hello, xxHash!"};
for (int i = 0; i < 100; i++)
{
hasher(objectToHash.data(), objectToHash.size());
}
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
15296278154063476002ULL);
}
void
testBigObjectWithMultiupleSmallUpdatesWithSeed()
{
testcase("Big Object With Multiuple Small Updates With Seed");
xxhasher hasher{static_cast<std::uint32_t>(103)};
std::string objectToHash{"Hello, xxHash!"};
for (int i = 0; i < 100; i++)
{
hasher(objectToHash.data(), objectToHash.size());
}
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
17285302196561698791ULL);
}
void
testBigObjectWithSmallAndBigUpdatesWithoutSeed()
{
testcase("Big Object With Small And Big Updates Without Seed");
xxhasher hasher{};
std::string objectToHash{"Hello, xxHash!"};
std::string bigObject;
for (int i = 0; i < 20; i++)
{
bigObject += "Hello, xxHash!";
}
hasher(objectToHash.data(), objectToHash.size());
hasher(bigObject.data(), bigObject.size());
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
1865045178324729219ULL);
}
void
testBigObjectWithSmallAndBigUpdatesWithSeed()
{
testcase("Big Object With Small And Big Updates With Seed");
xxhasher hasher{static_cast<std::uint32_t>(103)};
std::string objectToHash{"Hello, xxHash!"};
std::string bigObject;
for (int i = 0; i < 20; i++)
{
bigObject += "Hello, xxHash!";
}
hasher(objectToHash.data(), objectToHash.size());
hasher(bigObject.data(), bigObject.size());
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
16189862915636005281ULL);
}
void
testBigObjectWithOneUpdateWithoutSeed()
{
testcase("Big Object With One Update Without Seed");
xxhasher hasher{};
std::string objectToHash;
for (int i = 0; i < 100; i++)
{
objectToHash += "Hello, xxHash!";
}
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
15296278154063476002ULL);
}
void
testBigObjectWithOneUpdateWithSeed()
{
testcase("Big Object With One Update With Seed");
xxhasher hasher{static_cast<std::uint32_t>(103)};
std::string objectToHash;
for (int i = 0; i < 100; i++)
{
objectToHash += "Hello, xxHash!";
}
hasher(objectToHash.data(), objectToHash.size());
BEAST_EXPECT(
static_cast<xxhasher::result_type>(hasher) ==
17285302196561698791ULL);
}
void
run() override
{
testWithoutSeed();
testWithSeed();
testWithTwoSeeds();
testBigObjectWithMultiupleSmallUpdatesWithoutSeed();
testBigObjectWithMultiupleSmallUpdatesWithSeed();
testBigObjectWithSmallAndBigUpdatesWithoutSeed();
testBigObjectWithSmallAndBigUpdatesWithSeed();
testBigObjectWithOneUpdateWithoutSeed();
testBigObjectWithOneUpdateWithSeed();
}
};
BEAST_DEFINE_TESTSUITE(XXHasher, beast_core, beast);
} // namespace beast