Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/cli/perf_hash_engine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* (C) 2026 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include "perf.h"

#if defined(BOTAN_HAS_HASH_ENGINES)

#include <botan/exceptn.h>
#include <botan/hash.h>
#include <botan/rng.h>
#include <botan/internal/fmt.h>
#include <botan/internal/hash_engine.h>

namespace Botan_CLI {

namespace {

class PerfTest_HashEngine final : public PerfTest {
public:
void go(const PerfConfig& config) override {
const std::vector<std::string> hashes = {"SHA-256", "SHA-512", "SHAKE-256(256)"};

const std::vector<size_t> msg_sizes = {64, 256, 1024, 16384};
const std::vector<size_t> counts = {16, 32, 128};

for(const auto& hash_fn : hashes) {
std::unique_ptr<Botan::Hash_Engine> engine;

try {
engine = Botan::Hash_Engine::create_or_throw(hash_fn);
} catch(Botan::Not_Implemented&) {
continue;
}

const size_t out_len = engine->output_length();

for(size_t msg_size : msg_sizes) {
for(size_t count : counts) {
std::vector<uint8_t> input_buf(count * msg_size);
std::vector<uint8_t> output_buf(count * out_len);

config.rng().randomize(input_buf);

std::vector<std::span<const uint8_t>> input_spans(count);
std::vector<std::span<uint8_t>> output_spans(count);

for(size_t i = 0; i < count; ++i) {
input_spans[i] = std::span<const uint8_t>(input_buf.data() + i * msg_size, msg_size);
output_spans[i] = std::span<uint8_t>(output_buf.data() + i * out_len, out_len);
}

const std::string name =
Botan::fmt("Hash_Engine({}) n={}", hash_fn, count);

const uint64_t total_bytes = static_cast<uint64_t>(count) * msg_size;
auto timer = config.make_timer(name, total_bytes, "batch_hash", engine->provider(), msg_size);

timer->run_until_elapsed(config.runtime(), [&]() { engine->batch_hash(output_spans, input_spans); });

config.record_result(*timer);
}
}
}
}
};

} // namespace

BOTAN_REGISTER_PERF_TEST("hash_engine", PerfTest_HashEngine);

} // namespace Botan_CLI

#endif
155 changes: 155 additions & 0 deletions src/lib/hash_engine/hash_engine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* (C) 2026 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/hash_engine.h>

#include <botan/assert.h>
#include <botan/hash.h>

#if defined(BOTAN_HAS_THREAD_UTILS)
#include <botan/internal/thread_pool.h>
#endif

namespace Botan {

namespace {

class Base_Hash_Engine final : public Hash_Engine {
public:
explicit Base_Hash_Engine(std::unique_ptr<HashFunction> hash) : m_hash(std::move(hash)) {}

std::string name() const override { return m_hash->name(); }

std::string provider() const override { return "base"; }

size_t output_length() const override { return m_hash->output_length(); }

size_t parallelism() const override { return 1; }

void batch_hash(std::span<std::span<uint8_t>> outputs, std::span<std::span<const uint8_t>> inputs) override {
BOTAN_ARG_CHECK(outputs.size() == inputs.size(),
"Hash_Engine::batch_hash requires same number of inputs and outputs");

for(size_t i = 0; i != inputs.size(); ++i) {
BOTAN_ARG_CHECK(outputs[i].size() >= output_length(), "Hash_Engine::batch_hash output buffer too small");
m_hash->update(inputs[i]);
m_hash->final(outputs[i].first(output_length()));
}
}

private:
std::unique_ptr<HashFunction> m_hash;
};

#if defined(BOTAN_HAS_THREAD_UTILS)

class Threaded_Hash_Engine final : public Hash_Engine {
public:
Threaded_Hash_Engine(std::unique_ptr<HashFunction> hash, Thread_Pool& threadpool) :
m_threadpool(threadpool),
m_name(hash->name()),
m_output_length(hash->output_length()),
m_thread_count(m_threadpool.worker_count()) {
BOTAN_ASSERT_NOMSG(m_thread_count > 0);
m_hashes.reserve(m_thread_count);
for(size_t i = 0; i != m_thread_count; ++i) {
m_hashes.push_back(hash->new_object());
}
}

std::string name() const override { return m_name; }

std::string provider() const override { return "threads"; }

size_t output_length() const override { return m_output_length; }

size_t parallelism() const override { return m_thread_count; }

void batch_hash(std::span<std::span<uint8_t>> outputs, std::span<std::span<const uint8_t>> inputs) override {
BOTAN_ARG_CHECK(outputs.size() == inputs.size(),
"Hash_Engine::batch_hash requires same number of inputs and outputs");

const size_t count = inputs.size();
const size_t threads = usable_threads(count);

if(threads <= 1) {
hash_chunk(*m_hashes[0], m_output_length, outputs, inputs, 0, count);
return;
}

dispatch(threads, count, [&](size_t t, size_t offset, size_t n) {
hash_chunk(*m_hashes[t], m_output_length, outputs, inputs, offset, n);
});
}

private:
size_t usable_threads(size_t count) const {
// Chunk work to not spread too thinly since just queuing the work
// in the pool has nonzero overhead.
constexpr size_t MIN_HASHES_PER_THREAD = 64;

const size_t max_useful_threads = count / MIN_HASHES_PER_THREAD;
return std::min(m_thread_count, std::max<size_t>(max_useful_threads, 1));
}

template <typename F>
void dispatch(size_t threads, size_t count, F work_fn) {
const size_t per_thread = count / threads;
const size_t remainder = count % threads;

std::vector<std::future<void>> futures;
futures.reserve(threads);

size_t offset = 0;
for(size_t t = 0; t != threads; ++t) {
// First remainder threads get 1 extra hash over the main batch
const size_t n = per_thread + (t < remainder ? 1 : 0);
futures.push_back(m_threadpool.run(work_fn, t, offset, n));
offset += n;
}

for(auto& f : futures) {
f.get();
}
}

static void hash_chunk(HashFunction& hash,
size_t output_length,
std::span<std::span<uint8_t>> outputs,
std::span<std::span<const uint8_t>> inputs,
size_t offset,
size_t count) {
for(size_t i = offset; i != offset + count; ++i) {
hash.update(inputs[i]);
hash.final(outputs[i].first(output_length));
}
}

Thread_Pool& m_threadpool;
std::string m_name;
size_t m_output_length;
size_t m_thread_count;
std::vector<std::unique_ptr<HashFunction>> m_hashes;
};

#endif

} // namespace

std::unique_ptr<Hash_Engine> Hash_Engine::create_or_throw(std::string_view hash_fn) {
auto hash = HashFunction::create_or_throw(hash_fn);

#if defined(BOTAN_HAS_THREAD_UTILS)
auto& threadpool = Thread_Pool::global_instance();
if(threadpool.worker_count() > 0) {
return std::make_unique<Threaded_Hash_Engine>(std::move(hash), threadpool);
}
#endif

return std::make_unique<Base_Hash_Engine>(std::move(hash));
}

} // namespace Botan
65 changes: 65 additions & 0 deletions src/lib/hash_engine/hash_engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* (C) 2026 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#ifndef BOTAN_HASH_ENGINE_H_
#define BOTAN_HASH_ENGINE_H_

#include <botan/types.h>
#include <memory>
#include <span>
#include <string>

namespace Botan {

class BOTAN_TEST_API Hash_Engine {
public:
/**
* @return name of the hash
*/
virtual std::string name() const = 0;

/**
* @return provider of the engine (eg "base", "avx2", "avx512")
*/
virtual std::string provider() const = 0;

/**
* @return output length of the hash function
*/
virtual size_t output_length() const = 0;

/**
* @return native parallelism of this implementation
*/
virtual size_t parallelism() const = 0;

/**
* Hash many inputs
*
* Each message must be exactly identical length
*/
virtual void batch_hash(std::span<std::span<uint8_t>> outputs, std::span<std::span<const uint8_t>> inputs) = 0;

/**
* Create a new Hash_Engine or throw Not_Implemented
*/
static std::unique_ptr<Hash_Engine> create_or_throw(std::string_view hash_fn);

Hash_Engine(const Hash_Engine& other) = delete;
Hash_Engine(Hash_Engine&& other) = delete;

Hash_Engine& operator=(const Hash_Engine& other) = delete;
Hash_Engine& operator=(Hash_Engine&& other) = delete;

virtual ~Hash_Engine() = default;

protected:
Hash_Engine() = default;
};

} // namespace Botan

#endif
16 changes: 16 additions & 0 deletions src/lib/hash_engine/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<internal_defines>
HASH_ENGINES -> 20260222
</internal_defines>

<module_info>
name -> "Hash Engine"
</module_info>

<requires>
hash
</requires>

<header:internal>
hash_engine.h
</header:internal>

Loading
Loading