//===-- Common utility class for differential analysis --------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "src/__support/CPP/algorithm.h" #include "src/__support/FPUtil/FPBits.h" #include "src/__support/macros/config.h" #include "test/src/math/performance_testing/Timer.h" #include #include namespace LIBC_NAMESPACE_DECL { namespace testing { template class PerfTest { using FPBits = fputil::FPBits; using StorageType = typename FPBits::StorageType; static constexpr StorageType U_INT_MAX = cpp::numeric_limits::max(); public: using BinaryFuncPtr = OutputType (*)(InputType, InputType); using UnaryFuncPtr = OutputType (*)(InputType); template static void run_perf_in_range(Func FuncA, Func FuncB, StorageType startingBit, StorageType endingBit, size_t N, size_t rounds, const char *name_a, const char *name_b, std::ofstream &log) { if (sizeof(StorageType) <= sizeof(size_t)) N = cpp::min(N, static_cast(endingBit - startingBit)); auto runner = [=](Func func) { [[maybe_unused]] volatile OutputType result; if (endingBit < startingBit) { return; } StorageType step = (endingBit - startingBit) / N; if (step == 0) step = 1; for (size_t i = 0; i < rounds; i++) { for (StorageType bits_x = startingBit, bits_y = endingBit;; bits_x += step, bits_y -= step) { InputType x = FPBits(bits_x).get_val(); if constexpr (binary) { InputType y = FPBits(bits_y).get_val(); result = func(x, y); } else { result = func(x); } if (endingBit - bits_x < step) { break; } } } }; Timer timer; timer.start(); runner(FuncA); timer.stop(); double a_average = static_cast(timer.nanoseconds()) / N / rounds; log << "-- Function A: " << name_a << " --\n"; log << " Total time : " << timer.nanoseconds() << " ns \n"; log << " Average runtime : " << a_average << " ns/op \n"; log << " Ops per second : " << static_cast(1'000'000'000.0 / a_average) << " op/s \n"; timer.start(); runner(FuncB); timer.stop(); double b_average = static_cast(timer.nanoseconds()) / N / rounds; log << "-- Function B: " << name_b << " --\n"; log << " Total time : " << timer.nanoseconds() << " ns \n"; log << " Average runtime : " << b_average << " ns/op \n"; log << " Ops per second : " << static_cast(1'000'000'000.0 / b_average) << " op/s \n"; log << "-- Average ops per second ratio --\n"; log << " A / B : " << b_average / a_average << " \n"; } template static void run_perf(Func FuncA, Func FuncB, int rounds, const char *name_a, const char *name_b, const char *logFile) { std::ofstream log(logFile); log << " Performance tests with inputs in denormal range:\n"; run_perf_in_range( FuncA, FuncB, /* startingBit= */ StorageType(0), /* endingBit= */ FPBits::max_subnormal().uintval(), 1'000'001, rounds, name_a, name_b, log); log << "\n Performance tests with inputs in normal range:\n"; run_perf_in_range(FuncA, FuncB, /* startingBit= */ FPBits::min_normal().uintval(), /* endingBit= */ FPBits::max_normal().uintval(), 1'000'001, rounds, name_a, name_b, log); log << "\n Performance tests with inputs in normal range with exponents " "close to each other:\n"; run_perf_in_range( FuncA, FuncB, /* startingBit= */ FPBits(OutputType(0x1.0p-10)).uintval(), /* endingBit= */ FPBits(OutputType(0x1.0p+10)).uintval(), 1'000'001, rounds, name_a, name_b, log); } }; } // namespace testing } // namespace LIBC_NAMESPACE_DECL #define BINARY_INPUT_SINGLE_OUTPUT_PERF(OutputType, InputType, FuncA, FuncB, \ filename) \ { \ using TargetFuncPtr = \ typename LIBC_NAMESPACE::testing::PerfTest::BinaryFuncPtr; \ LIBC_NAMESPACE::testing::PerfTest::run_perf( \ static_cast(&FuncA), \ static_cast(&FuncB), 1, #FuncA, #FuncB, filename); \ return 0; \ } #define BINARY_INPUT_SINGLE_OUTPUT_PERF_EX(OutputType, InputType, FuncA, \ FuncB, rounds, filename) \ { \ using TargetFuncPtr = \ typename LIBC_NAMESPACE::testing::PerfTest::BinaryFuncPtr; \ LIBC_NAMESPACE::testing::PerfTest::run_perf( \ static_cast(&FuncA), \ static_cast(&FuncB), rounds, #FuncA, #FuncB, filename); \ return 0; \ } #define SINGLE_INPUT_SINGLE_OUTPUT_PERF(T, FuncA, FuncB, filename) \ { \ using TargetFuncPtr = \ typename LIBC_NAMESPACE::testing::PerfTest::UnaryFuncPtr; \ LIBC_NAMESPACE::testing::PerfTest::run_perf( \ static_cast(&FuncA), \ static_cast(&FuncB), 1, #FuncA, #FuncB, filename); \ return 0; \ } #define SINGLE_INPUT_SINGLE_OUTPUT_PERF_EX(T, FuncA, FuncB, rounds, filename) \ { \ using TargetFuncPtr = \ typename LIBC_NAMESPACE::testing::PerfTest::UnaryFuncPtr; \ LIBC_NAMESPACE::testing::PerfTest::run_perf( \ static_cast(&FuncA), \ static_cast(&FuncB), rounds, #FuncA, #FuncB, filename); \ return 0; \ }