1 //===-- Common utility class for differential analysis --------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "src/__support/CPP/algorithm.h" 10 #include "src/__support/FPUtil/FPBits.h" 11 #include "src/__support/macros/config.h" 12 #include "test/src/math/performance_testing/Timer.h" 13 14 #include <cstddef> 15 #include <fstream> 16 17 namespace LIBC_NAMESPACE_DECL { 18 namespace testing { 19 template <typename OutputType, typename InputType> 20 class BinaryOpSingleOutputPerf { 21 using FPBits = fputil::FPBits<OutputType>; 22 using StorageType = typename FPBits::StorageType; 23 static constexpr StorageType UIntMax = 24 cpp::numeric_limits<StorageType>::max(); 25 26 public: 27 typedef OutputType Func(InputType, InputType); 28 run_perf_in_range(Func myFunc,Func otherFunc,StorageType startingBit,StorageType endingBit,size_t N,size_t rounds,std::ofstream & log)29 static void run_perf_in_range(Func myFunc, Func otherFunc, 30 StorageType startingBit, StorageType endingBit, 31 size_t N, size_t rounds, std::ofstream &log) { 32 if (sizeof(StorageType) <= sizeof(size_t)) 33 N = cpp::min(N, static_cast<size_t>(endingBit - startingBit)); 34 35 auto runner = [=](Func func) { 36 [[maybe_unused]] volatile OutputType result; 37 if (endingBit < startingBit) { 38 return; 39 } 40 41 StorageType step = (endingBit - startingBit) / N; 42 for (size_t i = 0; i < rounds; i++) { 43 for (StorageType bitsX = startingBit, bitsY = endingBit;; 44 bitsX += step, bitsY -= step) { 45 InputType x = FPBits(bitsX).get_val(); 46 InputType y = FPBits(bitsY).get_val(); 47 result = func(x, y); 48 if (endingBit - bitsX < step) { 49 break; 50 } 51 } 52 } 53 }; 54 55 Timer timer; 56 timer.start(); 57 runner(myFunc); 58 timer.stop(); 59 60 double my_average = static_cast<double>(timer.nanoseconds()) / N / rounds; 61 log << "-- My function --\n"; 62 log << " Total time : " << timer.nanoseconds() << " ns \n"; 63 log << " Average runtime : " << my_average << " ns/op \n"; 64 log << " Ops per second : " 65 << static_cast<uint64_t>(1'000'000'000.0 / my_average) << " op/s \n"; 66 67 timer.start(); 68 runner(otherFunc); 69 timer.stop(); 70 71 double other_average = 72 static_cast<double>(timer.nanoseconds()) / N / rounds; 73 log << "-- Other function --\n"; 74 log << " Total time : " << timer.nanoseconds() << " ns \n"; 75 log << " Average runtime : " << other_average << " ns/op \n"; 76 log << " Ops per second : " 77 << static_cast<uint64_t>(1'000'000'000.0 / other_average) << " op/s \n"; 78 79 log << "-- Average runtime ratio --\n"; 80 log << " Mine / Other's : " << my_average / other_average << " \n"; 81 } 82 run_perf(Func myFunc,Func otherFunc,int rounds,const char * logFile)83 static void run_perf(Func myFunc, Func otherFunc, int rounds, 84 const char *logFile) { 85 std::ofstream log(logFile); 86 log << " Performance tests with inputs in denormal range:\n"; 87 run_perf_in_range(myFunc, otherFunc, /* startingBit= */ StorageType(0), 88 /* endingBit= */ FPBits::max_subnormal().uintval(), 89 1'000'001, rounds, log); 90 log << "\n Performance tests with inputs in normal range:\n"; 91 run_perf_in_range(myFunc, otherFunc, 92 /* startingBit= */ FPBits::min_normal().uintval(), 93 /* endingBit= */ FPBits::max_normal().uintval(), 94 1'000'001, rounds, log); 95 log << "\n Performance tests with inputs in normal range with exponents " 96 "close to each other:\n"; 97 run_perf_in_range( 98 myFunc, otherFunc, 99 /* startingBit= */ FPBits(OutputType(0x1.0p-10)).uintval(), 100 /* endingBit= */ FPBits(OutputType(0x1.0p+10)).uintval(), 1'000'001, 101 rounds, log); 102 } 103 run_diff(Func myFunc,Func otherFunc,const char * logFile)104 static void run_diff(Func myFunc, Func otherFunc, const char *logFile) { 105 uint64_t diffCount = 0; 106 std::ofstream log(logFile); 107 log << " Diff tests with inputs in denormal range:\n"; 108 diffCount += run_diff_in_range( 109 myFunc, otherFunc, /* startingBit= */ StorageType(0), 110 /* endingBit= */ FPBits::max_subnormal().uintval(), 1'000'001, log); 111 log << "\n Diff tests with inputs in normal range:\n"; 112 diffCount += run_diff_in_range( 113 myFunc, otherFunc, 114 /* startingBit= */ FPBits::min_normal().uintval(), 115 /* endingBit= */ FPBits::max_normal().uintval(), 100'000'001, log); 116 log << "\n Diff tests with inputs in normal range with exponents " 117 "close to each other:\n"; 118 diffCount += run_diff_in_range( 119 myFunc, otherFunc, 120 /* startingBit= */ FPBits(OutputType(0x1.0p-10)).uintval(), 121 /* endingBit= */ FPBits(OutputType(0x1.0p+10)).uintval(), 10'000'001, 122 log); 123 124 log << "Total number of differing results: " << diffCount << '\n'; 125 } 126 }; 127 128 } // namespace testing 129 } // namespace LIBC_NAMESPACE_DECL 130 131 #define BINARY_OP_SINGLE_OUTPUT_PERF(OutputType, InputType, myFunc, otherFunc, \ 132 filename) \ 133 int main() { \ 134 LIBC_NAMESPACE::testing::BinaryOpSingleOutputPerf< \ 135 OutputType, InputType>::run_perf(&myFunc, &otherFunc, 1, filename); \ 136 return 0; \ 137 } 138 139 #define BINARY_OP_SINGLE_OUTPUT_PERF_EX(OutputType, InputType, myFunc, \ 140 otherFunc, rounds, filename) \ 141 { \ 142 LIBC_NAMESPACE::testing::BinaryOpSingleOutputPerf< \ 143 OutputType, InputType>::run_perf(&myFunc, &otherFunc, rounds, \ 144 filename); \ 145 LIBC_NAMESPACE::testing::BinaryOpSingleOutputPerf< \ 146 OutputType, InputType>::run_perf(&myFunc, &otherFunc, rounds, \ 147 filename); \ 148 } 149