diff --git a/.gitignore b/.gitignore index cf46c7c..da3705c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /build /Testing /cmake-build-debug +/build-bench .vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e1149c..ce3c918 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,4 +9,11 @@ set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(src) include(CTest) -add_subdirectory(tests) \ No newline at end of file +add_subdirectory(tests) + +# Benchmarks are disabled by default +option(BIGINT_BUILD_BENCHMARKS "Build BigInt benchmarks" ON) + +if(BIGINT_BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() \ No newline at end of file diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..7464398 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,26 @@ +include(FetchContent) + +# Disable Google Benchmark's own tests +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.9.5 + GIT_SHALLOW TRUE +) + +FetchContent_MakeAvailable(googlebenchmark) + +add_executable(BigInt-Benchmarks + benchmark_bigint.cpp +) + +target_link_libraries( + BigInt-Benchmarks + PRIVATE + BigInt + benchmark::benchmark_main +) \ No newline at end of file diff --git a/benchmarks/benchmark_bigint.cpp b/benchmarks/benchmark_bigint.cpp new file mode 100644 index 0000000..dbeb989 --- /dev/null +++ b/benchmarks/benchmark_bigint.cpp @@ -0,0 +1,184 @@ +#include + +#include "bigint.h" +#include "math_utils.h" + +#include +#include + +using operations::BigInt; + +namespace { + +// Erstellt einen BigInt mit ungefähr/exakt der gewünschten Limb-Größe. +// Da ein Limb uint64_t ist: +// 2048 Bit = 32 Limbs. +BigInt makeBigInt(const std::size_t bits, const std::uint64_t pattern) { + const std::size_t limbCount = (bits + 63) / 64; + + ByteArray limbs(limbCount, pattern); + + return BigInt(std::move(limbs)); +} + + +// ------------------------------------------------------------ +// Addition +// ------------------------------------------------------------ + +static void BM_Add(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + const BigInt a = makeBigInt(bits, 0xAAAAAAAAAAAAAAAAULL); + const BigInt b = makeBigInt(bits, 0x5555555555555555ULL); + + for (auto _ : state) { + BigInt result = a + b; + + benchmark::DoNotOptimize(result); + } + + state.SetComplexityN(static_cast(bits / 64)); +} + +BENCHMARK(BM_Add) + ->RangeMultiplier(2) + ->Range(64, 8192) + ->Complexity(); + + +// ------------------------------------------------------------ +// Subtraction +// ------------------------------------------------------------ + +static void BM_Subtract(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + // a > b, wichtig bei unsigned BigInt + const BigInt a = makeBigInt(bits, 0xFFFFFFFFFFFFFFFFULL); + const BigInt b = makeBigInt(bits, 0x5555555555555555ULL); + + for (auto _ : state) { + BigInt result = a - b; + + benchmark::DoNotOptimize(result); + } + + state.SetComplexityN(static_cast(bits / 64)); +} + +BENCHMARK(BM_Subtract) + ->RangeMultiplier(2) + ->Range(64, 8192) + ->Complexity(); + + +// ------------------------------------------------------------ +// Multiplikation +// ------------------------------------------------------------ + +static void BM_Multiply(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + const BigInt a = makeBigInt(bits, 0xAAAAAAAAAAAAAAAAULL); + const BigInt b = makeBigInt(bits, 0x5555555555555555ULL); + + for (auto _ : state) { + BigInt result = a * b; + + benchmark::DoNotOptimize(result); + } + + state.SetComplexityN(static_cast(bits / 64)); +} + +BENCHMARK(BM_Multiply) + ->RangeMultiplier(2) + ->Range(64, 8192) + ->Complexity(); + + +// ------------------------------------------------------------ +// Division +// ------------------------------------------------------------ + +static void BM_Divide(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + // a > b + const BigInt a = makeBigInt(bits, 0xFFFFFFFFFFFFFFFFULL); + const BigInt b = makeBigInt(bits, 0x5555555555555555ULL); + + for (auto _ : state) { + BigInt result = a / b; + + benchmark::DoNotOptimize(result); + } + + state.SetComplexityN(static_cast(bits / 64)); +} + +BENCHMARK(BM_Divide) + ->RangeMultiplier(2) + ->Range(64, 4096) + ->Complexity(); + + +// ------------------------------------------------------------ +// Modulo +// ------------------------------------------------------------ + +static void BM_Modulo(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + const BigInt a = makeBigInt(bits, 0xFFFFFFFFFFFFFFFFULL); + const BigInt b = makeBigInt(bits, 0x5555555555555555ULL); + + for (auto _ : state) { + BigInt result = a % b; + + benchmark::DoNotOptimize(result); + } + + state.SetComplexityN(static_cast(bits / 64)); +} + +BENCHMARK(BM_Modulo) + ->RangeMultiplier(2) + ->Range(64, 4096) + ->Complexity(); + + +// ------------------------------------------------------------ +// Modular Exponentiation +// +// Verwendet exponent = 65537. +// Das ist für einen ersten Benchmark wesentlich sinnvoller als +// direkt einen mehrere tausend Bit großen Exponenten zu verwenden. +// ------------------------------------------------------------ + +static void BM_ModPow65537(benchmark::State& state) { + const auto bits = static_cast(state.range(0)); + + const BigInt base = + makeBigInt(bits, 0x5555555555555555ULL); + + const BigInt exponent(65537); + + // modulus > base + const BigInt modulus = + makeBigInt(bits, 0xFFFFFFFFFFFFFFFFULL); + + for (auto _ : state) { + BigInt result = + operations::math::modPow(base, exponent, modulus); + + benchmark::DoNotOptimize(result); + } +} + +BENCHMARK(BM_ModPow65537) + ->RangeMultiplier(2) + ->Range(256, 2048); + +} // namespace \ No newline at end of file diff --git a/src/bigint.cpp b/src/bigint.cpp index 0ad2b73..974e6d9 100644 --- a/src/bigint.cpp +++ b/src/bigint.cpp @@ -133,58 +133,58 @@ void operations::BigInt::mul(const ByteArray &b) noexcept { for (size_t i = 0; i < size_a; ++i) { std::uint64_t carry = 0; - std::uint64_t valA = data[i]; + const std::uint64_t valA = data[i]; - for (size_t j = 0; j < size_b; ++j) { - std::uint64_t valB = b[j]; + const std::uint64_t n1 = valA >> 32; + const std::uint64_t n2 = valA & 0xFFFFFFFFULL; - std::uint64_t n1 = valA >> 32; - std::uint64_t n2 = valA & 0xFFFFFFFFULL; + for (size_t j = 0; j < size_b; ++j) { + const std::uint64_t valB = b[j]; - std::uint64_t n3 = valB >> 32; - std::uint64_t n4 = valB & 0xFFFFFFFFULL; + const std::uint64_t n3 = valB >> 32; + const std::uint64_t n4 = valB & 0xFFFFFFFFULL; - std::uint64_t r1 = n1 * n3; + const std::uint64_t r1 = n1 * n3; std::uint64_t r2 = n2 * n4; - std::uint64_t r3 = n1 * n4; - std::uint64_t r4 = n2 * n3; + const std::uint64_t r3 = n1 * n4; + const std::uint64_t r4 = n2 * n3; - std::uint64_t c1 = r1 >> 32; - std::uint64_t c2 = r3 >> 32; - std::uint64_t fr1 = (r1 << 32) | (r3 & 0xFFFFFFFFULL); - std::uint64_t fc1 = (c1 << 32) | c2; + const std::uint64_t c1 = r1 >> 32; + const std::uint64_t c2 = r3 >> 32; + const std::uint64_t fr1 = (r1 << 32) | (r3 & 0xFFFFFFFFULL); + const std::uint64_t fc1 = (c1 << 32) | c2; - std::uint64_t c3 = r2 >> 32; + const std::uint64_t c3 = r2 >> 32; r2 &= 0xFFFFFFFFULL; - std::uint64_t c4 = r4 >> 32; - std::uint64_t fr2 = (r4 << 32) | r2; - std::uint64_t fc2 = c3 | (c4 << 32); + const std::uint64_t c4 = r4 >> 32; + const std::uint64_t fr2 = (r4 << 32) | r2; + const std::uint64_t fc2 = c3 | (c4 << 32); std::uint64_t fl = fr2; std::uint64_t runningCarry = 0; - std::uint64_t valFL = fc2 << 32; + const std::uint64_t valFL = fc2 << 32; fl += valFL; if (valFL > fl) { runningCarry += 1ULL; } - std::uint64_t valFH = fr1 << 32; + const std::uint64_t valFH = fr1 << 32; fl += valFH; if (valFH > fl) { runningCarry += 1ULL; } - std::uint64_t fh = (fc2 >> 32) + (fr1 >> 32) + fc1 + runningCarry; + const std::uint64_t fh = (fc2 >> 32) + (fr1 >> 32) + fc1 + runningCarry; - std::uint64_t current = result[i + j]; + const std::uint64_t current = result[i + j]; std::uint64_t sum_low = current + fl; - std::uint64_t carry_out1 = (sum_low < current) ? 1ULL : 0ULL; + const std::uint64_t carry_out1 = (sum_low < current) ? 1ULL : 0ULL; - std::uint64_t sum_low_prev = sum_low; + const std::uint64_t sum_low_prev = sum_low; sum_low += carry; - std::uint64_t carry_out2 = (sum_low < sum_low_prev) ? 1ULL : 0ULL; + const std::uint64_t carry_out2 = (sum_low < sum_low_prev) ? 1ULL : 0ULL; result[i + j] = sum_low;