Utilities: Add a crypto benchmarking tool

This commit is contained in:
Ali Mohammad Pur 2024-06-08 17:18:01 +02:00 committed by Ali Mohammad Pur
parent 3d61428bb0
commit bf5c2d0859
4 changed files with 263 additions and 1 deletions

View file

@ -611,6 +611,7 @@ if (BUILD_LAGOM)
lagom_utility(xml SOURCES ../../Userland/Utilities/xml.cpp LIBS LibFileSystem LibMain LibXML LibURL)
lagom_utility(xzcat SOURCES ../../Userland/Utilities/xzcat.cpp LIBS LibCompress LibMain)
lagom_utility(fdtdump SOURCES ../../Userland/Utilities/fdtdump.cpp LIBS LibDeviceTree LibMain)
lagom_utility(crypto-bench SOURCES ../../Userland/Utilities/crypto-bench.cpp LIBS LibMain LibCrypto)
enable_testing()
# LibTest

View file

@ -63,7 +63,7 @@ public:
{
VERIFY(!ivec.is_empty());
static ByteBuffer dummy;
static ByteBuffer dummy = MUST(ByteBuffer::create_uninitialized(out.size()));
encrypt(in, out, ivec, dummy, dummy);
}

View file

@ -90,6 +90,7 @@ target_link_libraries(cp PRIVATE LibFileSystem)
target_link_libraries(cpp-lexer PRIVATE LibCpp)
target_link_libraries(cpp-parser PRIVATE LibCpp)
target_link_libraries(cpp-preprocessor PRIVATE LibCpp)
target_link_libraries(crypto-bench PRIVATE LibCrypto)
target_link_libraries(diff PRIVATE LibDiff)
target_link_libraries(disasm PRIVATE LibELF LibX86)
target_link_libraries(drain PRIVATE LibFileSystem)

View file

@ -0,0 +1,260 @@
/*
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/HashMap.h>
#include <AK/NumberFormat.h>
#include <AK/Random.h>
#include <AK/Tuple.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCrypto/AEAD/ChaCha20Poly1305.h>
#include <LibCrypto/Authentication/GHash.h>
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/Authentication/Poly1305.h>
#include <LibCrypto/Checksum/Adler32.h>
#include <LibCrypto/Checksum/CRC32.h>
#include <LibCrypto/Cipher/AES.h>
#include <LibCrypto/Cipher/ChaCha20.h>
#include <LibCrypto/Forward.h>
#include <LibCrypto/Hash/BLAKE2b.h>
#include <LibCrypto/Hash/MD5.h>
#include <LibCrypto/Hash/MGF.h>
#include <LibCrypto/Hash/PBKDF2.h>
#include <LibCrypto/Hash/SHA1.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibMain/Main.h>
#define ALL_ALGORITHMS(E) \
E(md5, hash, Hash::MD5) \
E(sha1, hash, Hash::SHA1) \
E(sha256, hash, Hash::SHA256) \
E(sha512, hash, Hash::SHA512) \
E(blake2b, hash, Hash::BLAKE2b) \
E(adler32, checksum, Checksum::Adler32) \
E(crc32, checksum, Checksum::CRC32) \
E(hmac_md5, auth, Authentication::HMAC<Crypto::Hash::MD5>) \
E(hmac_sha1, auth, Authentication::HMAC<Crypto::Hash::SHA1>) \
E(hmac_sha256, auth, Authentication::HMAC<Crypto::Hash::SHA256>) \
E(hmac_sha512, auth, Authentication::HMAC<Crypto::Hash::SHA512>) \
E(poly1305, auth, Authentication::Poly1305) \
E(ghash, auth, Authentication::GHash) \
E(aes_128_cbc, cipher, Cipher::AESCipher::CBCMode, 128) \
E(aes_128_ctr, cipher, Cipher::AESCipher::CTRMode, 128) \
E(aes_128_gcm, cipher, Cipher::AESCipher::GCMMode, 128) \
E(aes_256_cbc, cipher, Cipher::AESCipher::CBCMode, 256) \
E(aes_256_ctr, cipher, Cipher::AESCipher::CTRMode, 256) \
E(chacha20_128, cipher, Cipher::ChaCha20, 128, 96) \
E(chacha20_256, cipher, Cipher::ChaCha20, 256, 96)
struct Timings {
u64 total_us { 0 };
u64 min_us { NumericLimits<u64>::max() };
u64 max_us { 0 };
size_t count { 0 };
size_t unit_bytes { 0 };
};
static HashMap<StringView, HashMap<size_t, Timings>> g_all_timings;
static auto g_time_slice_per_size = Duration::from_seconds(3);
constexpr size_t sizes_in_bytes[] = { 16, 1 * KiB, 16 * KiB, 256 * KiB, 1 * MiB, 16 * MiB };
static void run_benchmark_with_all_sizes(StringView name, Function<void(ByteBuffer&)> func)
{
for (auto size : sizes_in_bytes) {
auto result = ByteBuffer::create_uninitialized(size);
if (result.is_error()) {
warnln("Failed to allocate buffer of size {}", size);
continue;
}
auto buffer = result.release_value();
fill_with_random(buffer);
Timings timing_result { .unit_bytes = size };
warn("Running benchmark for {} with size {} for ~{}ms...", name, size, g_time_slice_per_size.to_milliseconds());
auto total_timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
for (; total_timer.elapsed_time() < g_time_slice_per_size;) {
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
func(buffer);
auto elapsed = timer.elapsed_time();
timing_result.max_us = max(timing_result.max_us, elapsed.to_microseconds());
timing_result.min_us = min(timing_result.min_us, elapsed.to_microseconds());
timing_result.total_us += elapsed.to_microseconds();
timing_result.count++;
}
g_all_timings.ensure(name).set(size, timing_result);
warnln("{}ms, {} ops, {}/s", total_timer.elapsed_milliseconds(), timing_result.count, human_readable_quantity(timing_result.unit_bytes * timing_result.count / timing_result.total_us * 1'000'000));
}
}
template<typename Algorithm>
static ErrorOr<void> run_hash_benchmark(StringView name)
{
run_benchmark_with_all_sizes(name, [](auto& buffer) {
auto digest = Algorithm::hash(buffer);
AK::taint_for_optimizer(digest);
});
return {};
}
template<typename Algorithm>
static ErrorOr<void> run_checksum_benchmark(StringView name)
{
run_benchmark_with_all_sizes(name, [](auto& buffer) {
Algorithm checksum;
checksum.update(buffer);
auto digest = checksum.digest();
AK::taint_for_optimizer(digest);
});
return {};
}
template<typename Algorithm>
static ErrorOr<void> run_auth_benchmark(StringView name)
{
auto key = TRY(ByteBuffer::create_uninitialized(128));
fill_with_random(key);
run_benchmark_with_all_sizes(name, [&](auto& buffer) {
Algorithm auth(key.bytes());
if constexpr (requires { auth.process(buffer.bytes()); }) {
auto tag = auth.process(buffer.bytes());
AK::taint_for_optimizer(tag);
} else if constexpr (IsSame<Algorithm, Crypto::Authentication::GHash>) {
auto tag = auth.process(buffer.bytes(), buffer.bytes());
AK::taint_for_optimizer(tag);
} else {
auth.update(buffer.bytes());
auto digest = auth.digest();
AK::taint_for_optimizer(digest);
}
});
return {};
}
template<typename Algorithm, typename... Options>
static ErrorOr<void> run_cipher_benchmark(StringView name, size_t key_bits, Options... options)
{
auto key = TRY(ByteBuffer::create_uninitialized(key_bits / 8));
fill_with_random(key);
auto out_buffer = TRY(ByteBuffer::create_uninitialized(16 * MiB));
auto iv = TRY(ByteBuffer::create_uninitialized(key_bits / 8));
fill_with_random(iv);
auto remaining_options = Tuple { options... };
ByteBuffer nonce;
constexpr auto needs_nonce = remaining_options.size() > 0;
if constexpr (needs_nonce) {
nonce = TRY(ByteBuffer::create_uninitialized(remaining_options.template get<0>()));
fill_with_random(nonce);
}
run_benchmark_with_all_sizes(name, [&](auto& buffer) {
Optional<Algorithm> cipher;
if constexpr (needs_nonce)
cipher = Algorithm(key.bytes(), nonce);
else
cipher = Algorithm(key.bytes(), key_bits, Crypto::Cipher::Intent::Encryption);
auto out_bytes = out_buffer.bytes();
if constexpr (requires { cipher->encrypt(buffer, out_bytes, iv.bytes()); })
cipher->encrypt(buffer, out_bytes, iv.bytes());
else
cipher->encrypt(buffer, out_bytes);
AK::taint_for_optimizer(out_buffer);
});
return {};
}
static ErrorOr<void> benchmark(StringView algorithm)
{
#define BENCH(name, type, algo, ...) \
if (algorithm == #name) { \
outln("Benchmarking {}...", #name); \
return run_##type##_benchmark<Crypto::algo>(#name##sv __VA_OPT__(, ) __VA_ARGS__); \
}
ALL_ALGORITHMS(BENCH);
#undef BENCH
return Error::from_string_literal("Unknown algorithm");
}
static void print_benchmark_results()
{
// algo, size, min, max, avg, throughput
outln("{:<20} {:<10} {:<10} {:<10} {:<10} {:<10}", "Algorithm", "Size", "Min us/op", "Max us/op", "Avg us/op", "Throughput");
for (auto& [algo, timings] : g_all_timings) {
for (auto size : sizes_in_bytes) {
auto t = timings.get(size);
if (!t.has_value())
continue;
auto& timing = t.value();
outln("{:<20} {:<10} {:<10} {:<10} {:<10} {:<10}/s",
algo,
human_readable_size(timing.unit_bytes),
timing.min_us,
timing.max_us,
timing.total_us / timing.count,
human_readable_quantity(timing.unit_bytes * timing.count / timing.total_us * 1'000'000));
}
}
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Vector<ByteString> algorithms;
Optional<StringView> time_slice;
Core::ArgsParser args_parser;
args_parser.add_positional_argument(algorithms, "Algorithms (or categories) to benchmark", "algorithms", Core::ArgsParser::Required::Yes);
args_parser.add_option(time_slice, "Time slice for each benchmark size in milliseconds", "time-slice", 't', "time-slice");
args_parser.add_option(Core::ArgsParser::Option {
.argument_mode = Core::ArgsParser::OptionArgumentMode::None,
.help_string = "List all available algorithms",
.long_name = "list",
.short_name = 'l',
.accept_value = [](auto) -> bool {
warnln("{:<20} {:<10}", "Algorithm", "Type");
#define BENCH(name, type, ...) \
warnln("{:<20} {:<10}", #name, #type);
ALL_ALGORITHMS(BENCH);
#undef BENCH
exit(0);
},
});
args_parser.set_general_help("Benchmark LibCrypto's implementations of various cryptographic algorithms");
args_parser.parse(arguments);
if (time_slice.has_value()) {
if (auto slice = time_slice->to_number<u32>(); slice.has_value())
g_time_slice_per_size = Duration::from_milliseconds(*slice);
else
return Error::from_string_literal("Invalid time slice value");
}
for (auto const& algorithm : algorithms) {
auto found = false;
#define BENCH(name, type, ...) \
if (algorithm == #type) { \
TRY(benchmark(#name##sv)); \
found = true; \
}
ALL_ALGORITHMS(BENCH);
#undef BENCH
if (!found)
TRY(benchmark(algorithm));
}
print_benchmark_results();
return 0;
}