bitcoin/src/bench/crypto_hash.cpp
Lőrinc c497ca6e91 bench: Add COutPoint and SaltedOutpointHasher benchmarks
This commit introduces new benchmarks to measure the performance of various operations using
SaltedOutpointHasher, including hash computation, set operations, and set creation.

These benchmarks are intended to provide insights about coin caching performance (e.g. during IBD).

> cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && build/src/bench/bench_bitcoin -filter='SaltedOutpointHasherBench.*' -min-time=10000

> C++ compiler .......................... AppleClang 16.0.0.16000026

|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|               58.60 |       17,065,922.04 |    0.3% |     11.02 | `SaltedOutpointHasherBench_create_set`
|               11.97 |       83,576,684.83 |    0.1% |     11.01 | `SaltedOutpointHasherBench_hash`
|               14.50 |       68,985,850.12 |    0.3% |     10.96 | `SaltedOutpointHasherBench_match`
|               13.90 |       71,942,033.47 |    0.4% |     11.03 | `SaltedOutpointHasherBench_mismatch`

> C++ compiler .......................... GNU 13.3.0

|               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|              136.76 |        7,312,133.16 |    0.0% |        1,086.67 |          491.12 |  2.213 |         119.54 |    1.1% |     11.01 | `SaltedOutpointHasherBench_create_set`
|               23.82 |       41,978,882.62 |    0.0% |          252.01 |           85.57 |  2.945 |           4.00 |    0.0% |     11.00 | `SaltedOutpointHasherBench_hash`
|               60.42 |       16,549,695.42 |    0.1% |          460.51 |          217.04 |  2.122 |          21.00 |    1.4% |     10.99 | `SaltedOutpointHasherBench_match`
|               78.66 |       12,713,595.35 |    0.1% |          555.59 |          282.52 |  1.967 |          20.19 |    2.2% |     10.74 | `SaltedOutpointHasherBench_mismatch`
2025-04-17 11:30:33 +02:00

385 lines
12 KiB
C++

// Copyright (c) 2016-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bench/bench.h>
#include <crypto/muhash.h>
#include <crypto/ripemd160.h>
#include <crypto/sha1.h>
#include <crypto/sha256.h>
#include <crypto/sha3.h>
#include <crypto/sha512.h>
#include <crypto/siphash.h>
#include <random.h>
#include <tinyformat.h>
#include <uint256.h>
#include <primitives/transaction.h>
#include <util/hasher.h>
#include <unordered_set>
#include <cstdint>
#include <vector>
/* Number of bytes to hash per iteration */
static const uint64_t BUFFER_SIZE = 1000*1000;
static void BenchRIPEMD160(benchmark::Bench& bench)
{
uint8_t hash[CRIPEMD160::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CRIPEMD160().Write(in.data(), in.size()).Finalize(hash);
});
}
static void SHA1(benchmark::Bench& bench)
{
uint8_t hash[CSHA1::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA1().Write(in.data(), in.size()).Finalize(hash);
});
}
static void SHA256_STANDARD(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::STANDARD)));
uint8_t hash[CSHA256::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256().Write(in.data(), in.size()).Finalize(hash);
});
SHA256AutoDetect();
}
static void SHA256_SSE4(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4)));
uint8_t hash[CSHA256::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256().Write(in.data(), in.size()).Finalize(hash);
});
SHA256AutoDetect();
}
static void SHA256_AVX2(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_AVX2)));
uint8_t hash[CSHA256::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256().Write(in.data(), in.size()).Finalize(hash);
});
SHA256AutoDetect();
}
static void SHA256_SHANI(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_SHANI)));
uint8_t hash[CSHA256::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256().Write(in.data(), in.size()).Finalize(hash);
});
SHA256AutoDetect();
}
static void SHA3_256_1M(benchmark::Bench& bench)
{
uint8_t hash[SHA3_256::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
SHA3_256().Write(in).Finalize(hash);
});
}
static void SHA256_32b_STANDARD(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::STANDARD)));
std::vector<uint8_t> in(32,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256()
.Write(in.data(), in.size())
.Finalize(in.data());
});
SHA256AutoDetect();
}
static void SHA256_32b_SSE4(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4)));
std::vector<uint8_t> in(32,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256()
.Write(in.data(), in.size())
.Finalize(in.data());
});
SHA256AutoDetect();
}
static void SHA256_32b_AVX2(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_AVX2)));
std::vector<uint8_t> in(32,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256()
.Write(in.data(), in.size())
.Finalize(in.data());
});
SHA256AutoDetect();
}
static void SHA256_32b_SHANI(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_SHANI)));
std::vector<uint8_t> in(32,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA256()
.Write(in.data(), in.size())
.Finalize(in.data());
});
SHA256AutoDetect();
}
static void SHA256D64_1024_STANDARD(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::STANDARD)));
std::vector<uint8_t> in(64 * 1024, 0);
bench.batch(in.size()).unit("byte").run([&] {
SHA256D64(in.data(), in.data(), 1024);
});
SHA256AutoDetect();
}
static void SHA256D64_1024_SSE4(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4)));
std::vector<uint8_t> in(64 * 1024, 0);
bench.batch(in.size()).unit("byte").run([&] {
SHA256D64(in.data(), in.data(), 1024);
});
SHA256AutoDetect();
}
static void SHA256D64_1024_AVX2(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_AVX2)));
std::vector<uint8_t> in(64 * 1024, 0);
bench.batch(in.size()).unit("byte").run([&] {
SHA256D64(in.data(), in.data(), 1024);
});
SHA256AutoDetect();
}
static void SHA256D64_1024_SHANI(benchmark::Bench& bench)
{
bench.name(strprintf("%s using the '%s' SHA256 implementation", __func__, SHA256AutoDetect(sha256_implementation::USE_SSE4_AND_SHANI)));
std::vector<uint8_t> in(64 * 1024, 0);
bench.batch(in.size()).unit("byte").run([&] {
SHA256D64(in.data(), in.data(), 1024);
});
SHA256AutoDetect();
}
static void SHA512(benchmark::Bench& bench)
{
uint8_t hash[CSHA512::OUTPUT_SIZE];
std::vector<uint8_t> in(BUFFER_SIZE,0);
bench.batch(in.size()).unit("byte").run([&] {
CSHA512().Write(in.data(), in.size()).Finalize(hash);
});
}
static void SipHash_32b(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
auto k0{rng.rand64()}, k1{rng.rand64()};
auto val{rng.rand256()};
auto i{0U};
bench.run([&] {
ankerl::nanobench::doNotOptimizeAway(SipHashUint256(k0, k1, val));
++k0;
++k1;
++i;
val.data()[i % uint256::size()] ^= i & 0xFF;
});
}
static void SaltedOutpointHasherBench_hash(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
constexpr size_t size{1000};
std::vector<COutPoint> outpoints(size);
for (auto& outpoint : outpoints) {
outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
}
const SaltedOutpointHasher hasher;
bench.batch(size).run([&] {
size_t result{0};
for (const auto& outpoint : outpoints) {
result ^= hasher(outpoint);
}
ankerl::nanobench::doNotOptimizeAway(result);
});
}
static void SaltedOutpointHasherBench_match(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
constexpr size_t size{1000};
std::unordered_set<COutPoint, SaltedOutpointHasher> values;
std::vector<COutPoint> value_vector;
values.reserve(size);
value_vector.reserve(size);
for (size_t i{0}; i < size; ++i) {
COutPoint outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
values.emplace(outpoint);
value_vector.push_back(outpoint);
assert(values.contains(outpoint));
}
bench.batch(size).run([&] {
bool result{true};
for (const auto& outpoint : value_vector) {
result ^= values.contains(outpoint);
}
ankerl::nanobench::doNotOptimizeAway(result);
});
}
static void SaltedOutpointHasherBench_mismatch(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
constexpr size_t size{1000};
std::unordered_set<COutPoint, SaltedOutpointHasher> values;
std::vector<COutPoint> missing_value_vector;
values.reserve(size);
missing_value_vector.reserve(size);
for (size_t i{0}; i < size; ++i) {
values.emplace(Txid::FromUint256(rng.rand256()), rng.rand32());
COutPoint missing_outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
missing_value_vector.push_back(missing_outpoint);
assert(!values.contains(missing_outpoint));
}
bench.batch(size).run([&] {
bool result{false};
for (const auto& outpoint : missing_value_vector) {
result ^= values.contains(outpoint);
}
ankerl::nanobench::doNotOptimizeAway(result);
});
}
static void SaltedOutpointHasherBench_create_set(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
constexpr size_t size{1000};
std::vector<COutPoint> outpoints(size);
for (auto& outpoint : outpoints) {
outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
}
bench.batch(size).run([&] {
std::unordered_set<COutPoint, SaltedOutpointHasher> set;
set.reserve(size);
for (const auto& outpoint : outpoints) {
set.emplace(outpoint);
}
ankerl::nanobench::doNotOptimizeAway(set.size());
});
}
static void MuHash(benchmark::Bench& bench)
{
MuHash3072 acc;
unsigned char key[32] = {0};
uint32_t i = 0;
bench.run([&] {
key[0] = ++i & 0xFF;
acc *= MuHash3072(key);
});
}
static void MuHashMul(benchmark::Bench& bench)
{
MuHash3072 acc;
FastRandomContext rng(true);
MuHash3072 muhash{rng.randbytes(32)};
bench.run([&] {
acc *= muhash;
});
}
static void MuHashDiv(benchmark::Bench& bench)
{
MuHash3072 acc;
FastRandomContext rng(true);
MuHash3072 muhash{rng.randbytes(32)};
bench.run([&] {
acc /= muhash;
});
}
static void MuHashPrecompute(benchmark::Bench& bench)
{
MuHash3072 acc;
FastRandomContext rng(true);
std::vector<unsigned char> key{rng.randbytes(32)};
bench.run([&] {
MuHash3072{key};
});
}
static void MuHashFinalize(benchmark::Bench& bench)
{
FastRandomContext rng(true);
MuHash3072 acc{rng.randbytes(32)};
acc /= MuHash3072{rng.rand256()};
bench.run([&] {
uint256 out;
acc.Finalize(out);
acc /= MuHash3072{out};
});
}
BENCHMARK(BenchRIPEMD160, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA1, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_STANDARD, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_SSE4, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_AVX2, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_SHANI, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA512, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA3_256_1M, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_32b_STANDARD, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_32b_SSE4, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_32b_AVX2, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256_32b_SHANI, benchmark::PriorityLevel::HIGH);
BENCHMARK(SipHash_32b, benchmark::PriorityLevel::HIGH);
BENCHMARK(SaltedOutpointHasherBench_hash, benchmark::PriorityLevel::HIGH);
BENCHMARK(SaltedOutpointHasherBench_match, benchmark::PriorityLevel::HIGH);
BENCHMARK(SaltedOutpointHasherBench_mismatch, benchmark::PriorityLevel::HIGH);
BENCHMARK(SaltedOutpointHasherBench_create_set, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256D64_1024_STANDARD, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256D64_1024_SSE4, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256D64_1024_AVX2, benchmark::PriorityLevel::HIGH);
BENCHMARK(SHA256D64_1024_SHANI, benchmark::PriorityLevel::HIGH);
BENCHMARK(MuHash, benchmark::PriorityLevel::HIGH);
BENCHMARK(MuHashMul, benchmark::PriorityLevel::HIGH);
BENCHMARK(MuHashDiv, benchmark::PriorityLevel::HIGH);
BENCHMARK(MuHashPrecompute, benchmark::PriorityLevel::HIGH);
BENCHMARK(MuHashFinalize, benchmark::PriorityLevel::HIGH);