xref: /aosp_15_r20/external/llvm-libc/test/src/math/performance_testing/BinaryOpSingleOutputPerf.h (revision 71db0c75aadcf003ffe3238005f61d7618a3fead)
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