| //===-- 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 <cstddef> |
| #include <fstream> |
| |
| namespace LIBC_NAMESPACE_DECL { |
| namespace testing { |
| template <typename OutputType, typename InputType> class PerfTest { |
| using FPBits = fputil::FPBits<OutputType>; |
| using StorageType = typename FPBits::StorageType; |
| static constexpr StorageType U_INT_MAX = |
| cpp::numeric_limits<StorageType>::max(); |
| |
| public: |
| using BinaryFuncPtr = OutputType (*)(InputType, InputType); |
| using UnaryFuncPtr = OutputType (*)(InputType); |
| |
| template <bool binary, typename Func> |
| 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<size_t>(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<double>(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<uint64_t>(1'000'000'000.0 / a_average) << " op/s \n"; |
| |
| timer.start(); |
| runner(FuncB); |
| timer.stop(); |
| |
| double b_average = static_cast<double>(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<uint64_t>(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 <bool binary, typename Func> |
| 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<binary>( |
| 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<binary>(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<binary>( |
| 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<OutputType, \ |
| InputType>::BinaryFuncPtr; \ |
| LIBC_NAMESPACE::testing::PerfTest<OutputType, InputType>::run_perf<true>( \ |
| static_cast<TargetFuncPtr>(&FuncA), \ |
| static_cast<TargetFuncPtr>(&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<OutputType, \ |
| InputType>::BinaryFuncPtr; \ |
| LIBC_NAMESPACE::testing::PerfTest<OutputType, InputType>::run_perf<true>( \ |
| static_cast<TargetFuncPtr>(&FuncA), \ |
| static_cast<TargetFuncPtr>(&FuncB), rounds, #FuncA, #FuncB, filename); \ |
| return 0; \ |
| } |
| |
| #define SINGLE_INPUT_SINGLE_OUTPUT_PERF(T, FuncA, FuncB, filename) \ |
| { \ |
| using TargetFuncPtr = \ |
| typename LIBC_NAMESPACE::testing::PerfTest<T, T>::UnaryFuncPtr; \ |
| LIBC_NAMESPACE::testing::PerfTest<T, T>::run_perf<false>( \ |
| static_cast<TargetFuncPtr>(&FuncA), \ |
| static_cast<TargetFuncPtr>(&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<T, T>::UnaryFuncPtr; \ |
| LIBC_NAMESPACE::testing::PerfTest<T, T>::run_perf<false>( \ |
| static_cast<TargetFuncPtr>(&FuncA), \ |
| static_cast<TargetFuncPtr>(&FuncB), rounds, #FuncA, #FuncB, filename); \ |
| return 0; \ |
| } |