xref: /aosp_15_r20/external/libcxx/utils/google-benchmark/src/complexity.cc (revision 58b9f456b02922dfdb1fad8a988d5fd8765ecb80)
1*58b9f456SAndroid Build Coastguard Worker // Copyright 2016 Ismael Jimenez Martinez. All rights reserved.
2*58b9f456SAndroid Build Coastguard Worker //
3*58b9f456SAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
4*58b9f456SAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
5*58b9f456SAndroid Build Coastguard Worker // You may obtain a copy of the License at
6*58b9f456SAndroid Build Coastguard Worker //
7*58b9f456SAndroid Build Coastguard Worker //     http://www.apache.org/licenses/LICENSE-2.0
8*58b9f456SAndroid Build Coastguard Worker //
9*58b9f456SAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*58b9f456SAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
11*58b9f456SAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*58b9f456SAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
13*58b9f456SAndroid Build Coastguard Worker // limitations under the License.
14*58b9f456SAndroid Build Coastguard Worker 
15*58b9f456SAndroid Build Coastguard Worker // Source project : https://github.com/ismaelJimenez/cpp.leastsq
16*58b9f456SAndroid Build Coastguard Worker // Adapted to be used with google benchmark
17*58b9f456SAndroid Build Coastguard Worker 
18*58b9f456SAndroid Build Coastguard Worker #include "benchmark/benchmark.h"
19*58b9f456SAndroid Build Coastguard Worker 
20*58b9f456SAndroid Build Coastguard Worker #include <algorithm>
21*58b9f456SAndroid Build Coastguard Worker #include <cmath>
22*58b9f456SAndroid Build Coastguard Worker #include "check.h"
23*58b9f456SAndroid Build Coastguard Worker #include "complexity.h"
24*58b9f456SAndroid Build Coastguard Worker 
25*58b9f456SAndroid Build Coastguard Worker namespace benchmark {
26*58b9f456SAndroid Build Coastguard Worker 
27*58b9f456SAndroid Build Coastguard Worker // Internal function to calculate the different scalability forms
FittingCurve(BigO complexity)28*58b9f456SAndroid Build Coastguard Worker BigOFunc* FittingCurve(BigO complexity) {
29*58b9f456SAndroid Build Coastguard Worker   static const double kLog2E = 1.44269504088896340736;
30*58b9f456SAndroid Build Coastguard Worker   switch (complexity) {
31*58b9f456SAndroid Build Coastguard Worker     case oN:
32*58b9f456SAndroid Build Coastguard Worker       return [](int64_t n) -> double { return static_cast<double>(n); };
33*58b9f456SAndroid Build Coastguard Worker     case oNSquared:
34*58b9f456SAndroid Build Coastguard Worker       return [](int64_t n) -> double { return std::pow(n, 2); };
35*58b9f456SAndroid Build Coastguard Worker     case oNCubed:
36*58b9f456SAndroid Build Coastguard Worker       return [](int64_t n) -> double { return std::pow(n, 3); };
37*58b9f456SAndroid Build Coastguard Worker     case oLogN:
38*58b9f456SAndroid Build Coastguard Worker       /* Note: can't use log2 because Android's GNU STL lacks it */
39*58b9f456SAndroid Build Coastguard Worker       return [](int64_t n) { return kLog2E * log(static_cast<double>(n)); };
40*58b9f456SAndroid Build Coastguard Worker     case oNLogN:
41*58b9f456SAndroid Build Coastguard Worker       /* Note: can't use log2 because Android's GNU STL lacks it */
42*58b9f456SAndroid Build Coastguard Worker       return [](int64_t n) { return kLog2E * n * log(static_cast<double>(n)); };
43*58b9f456SAndroid Build Coastguard Worker     case o1:
44*58b9f456SAndroid Build Coastguard Worker     default:
45*58b9f456SAndroid Build Coastguard Worker       return [](int64_t) { return 1.0; };
46*58b9f456SAndroid Build Coastguard Worker   }
47*58b9f456SAndroid Build Coastguard Worker }
48*58b9f456SAndroid Build Coastguard Worker 
49*58b9f456SAndroid Build Coastguard Worker // Function to return an string for the calculated complexity
GetBigOString(BigO complexity)50*58b9f456SAndroid Build Coastguard Worker std::string GetBigOString(BigO complexity) {
51*58b9f456SAndroid Build Coastguard Worker   switch (complexity) {
52*58b9f456SAndroid Build Coastguard Worker     case oN:
53*58b9f456SAndroid Build Coastguard Worker       return "N";
54*58b9f456SAndroid Build Coastguard Worker     case oNSquared:
55*58b9f456SAndroid Build Coastguard Worker       return "N^2";
56*58b9f456SAndroid Build Coastguard Worker     case oNCubed:
57*58b9f456SAndroid Build Coastguard Worker       return "N^3";
58*58b9f456SAndroid Build Coastguard Worker     case oLogN:
59*58b9f456SAndroid Build Coastguard Worker       return "lgN";
60*58b9f456SAndroid Build Coastguard Worker     case oNLogN:
61*58b9f456SAndroid Build Coastguard Worker       return "NlgN";
62*58b9f456SAndroid Build Coastguard Worker     case o1:
63*58b9f456SAndroid Build Coastguard Worker       return "(1)";
64*58b9f456SAndroid Build Coastguard Worker     default:
65*58b9f456SAndroid Build Coastguard Worker       return "f(N)";
66*58b9f456SAndroid Build Coastguard Worker   }
67*58b9f456SAndroid Build Coastguard Worker }
68*58b9f456SAndroid Build Coastguard Worker 
69*58b9f456SAndroid Build Coastguard Worker // Find the coefficient for the high-order term in the running time, by
70*58b9f456SAndroid Build Coastguard Worker // minimizing the sum of squares of relative error, for the fitting curve
71*58b9f456SAndroid Build Coastguard Worker // given by the lambda expression.
72*58b9f456SAndroid Build Coastguard Worker //   - n             : Vector containing the size of the benchmark tests.
73*58b9f456SAndroid Build Coastguard Worker //   - time          : Vector containing the times for the benchmark tests.
74*58b9f456SAndroid Build Coastguard Worker //   - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };).
75*58b9f456SAndroid Build Coastguard Worker 
76*58b9f456SAndroid Build Coastguard Worker // For a deeper explanation on the algorithm logic, please refer to
77*58b9f456SAndroid Build Coastguard Worker // https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics
78*58b9f456SAndroid Build Coastguard Worker 
MinimalLeastSq(const std::vector<int64_t> & n,const std::vector<double> & time,BigOFunc * fitting_curve)79*58b9f456SAndroid Build Coastguard Worker LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
80*58b9f456SAndroid Build Coastguard Worker                        const std::vector<double>& time,
81*58b9f456SAndroid Build Coastguard Worker                        BigOFunc* fitting_curve) {
82*58b9f456SAndroid Build Coastguard Worker   double sigma_gn = 0.0;
83*58b9f456SAndroid Build Coastguard Worker   double sigma_gn_squared = 0.0;
84*58b9f456SAndroid Build Coastguard Worker   double sigma_time = 0.0;
85*58b9f456SAndroid Build Coastguard Worker   double sigma_time_gn = 0.0;
86*58b9f456SAndroid Build Coastguard Worker 
87*58b9f456SAndroid Build Coastguard Worker   // Calculate least square fitting parameter
88*58b9f456SAndroid Build Coastguard Worker   for (size_t i = 0; i < n.size(); ++i) {
89*58b9f456SAndroid Build Coastguard Worker     double gn_i = fitting_curve(n[i]);
90*58b9f456SAndroid Build Coastguard Worker     sigma_gn += gn_i;
91*58b9f456SAndroid Build Coastguard Worker     sigma_gn_squared += gn_i * gn_i;
92*58b9f456SAndroid Build Coastguard Worker     sigma_time += time[i];
93*58b9f456SAndroid Build Coastguard Worker     sigma_time_gn += time[i] * gn_i;
94*58b9f456SAndroid Build Coastguard Worker   }
95*58b9f456SAndroid Build Coastguard Worker 
96*58b9f456SAndroid Build Coastguard Worker   LeastSq result;
97*58b9f456SAndroid Build Coastguard Worker   result.complexity = oLambda;
98*58b9f456SAndroid Build Coastguard Worker 
99*58b9f456SAndroid Build Coastguard Worker   // Calculate complexity.
100*58b9f456SAndroid Build Coastguard Worker   result.coef = sigma_time_gn / sigma_gn_squared;
101*58b9f456SAndroid Build Coastguard Worker 
102*58b9f456SAndroid Build Coastguard Worker   // Calculate RMS
103*58b9f456SAndroid Build Coastguard Worker   double rms = 0.0;
104*58b9f456SAndroid Build Coastguard Worker   for (size_t i = 0; i < n.size(); ++i) {
105*58b9f456SAndroid Build Coastguard Worker     double fit = result.coef * fitting_curve(n[i]);
106*58b9f456SAndroid Build Coastguard Worker     rms += pow((time[i] - fit), 2);
107*58b9f456SAndroid Build Coastguard Worker   }
108*58b9f456SAndroid Build Coastguard Worker 
109*58b9f456SAndroid Build Coastguard Worker   // Normalized RMS by the mean of the observed values
110*58b9f456SAndroid Build Coastguard Worker   double mean = sigma_time / n.size();
111*58b9f456SAndroid Build Coastguard Worker   result.rms = sqrt(rms / n.size()) / mean;
112*58b9f456SAndroid Build Coastguard Worker 
113*58b9f456SAndroid Build Coastguard Worker   return result;
114*58b9f456SAndroid Build Coastguard Worker }
115*58b9f456SAndroid Build Coastguard Worker 
116*58b9f456SAndroid Build Coastguard Worker // Find the coefficient for the high-order term in the running time, by
117*58b9f456SAndroid Build Coastguard Worker // minimizing the sum of squares of relative error.
118*58b9f456SAndroid Build Coastguard Worker //   - n          : Vector containing the size of the benchmark tests.
119*58b9f456SAndroid Build Coastguard Worker //   - time       : Vector containing the times for the benchmark tests.
120*58b9f456SAndroid Build Coastguard Worker //   - complexity : If different than oAuto, the fitting curve will stick to
121*58b9f456SAndroid Build Coastguard Worker //                  this one. If it is oAuto, it will be calculated the best
122*58b9f456SAndroid Build Coastguard Worker //                  fitting curve.
MinimalLeastSq(const std::vector<int64_t> & n,const std::vector<double> & time,const BigO complexity)123*58b9f456SAndroid Build Coastguard Worker LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
124*58b9f456SAndroid Build Coastguard Worker                        const std::vector<double>& time, const BigO complexity) {
125*58b9f456SAndroid Build Coastguard Worker   CHECK_EQ(n.size(), time.size());
126*58b9f456SAndroid Build Coastguard Worker   CHECK_GE(n.size(), 2);  // Do not compute fitting curve is less than two
127*58b9f456SAndroid Build Coastguard Worker                           // benchmark runs are given
128*58b9f456SAndroid Build Coastguard Worker   CHECK_NE(complexity, oNone);
129*58b9f456SAndroid Build Coastguard Worker 
130*58b9f456SAndroid Build Coastguard Worker   LeastSq best_fit;
131*58b9f456SAndroid Build Coastguard Worker 
132*58b9f456SAndroid Build Coastguard Worker   if (complexity == oAuto) {
133*58b9f456SAndroid Build Coastguard Worker     std::vector<BigO> fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed};
134*58b9f456SAndroid Build Coastguard Worker 
135*58b9f456SAndroid Build Coastguard Worker     // Take o1 as default best fitting curve
136*58b9f456SAndroid Build Coastguard Worker     best_fit = MinimalLeastSq(n, time, FittingCurve(o1));
137*58b9f456SAndroid Build Coastguard Worker     best_fit.complexity = o1;
138*58b9f456SAndroid Build Coastguard Worker 
139*58b9f456SAndroid Build Coastguard Worker     // Compute all possible fitting curves and stick to the best one
140*58b9f456SAndroid Build Coastguard Worker     for (const auto& fit : fit_curves) {
141*58b9f456SAndroid Build Coastguard Worker       LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit));
142*58b9f456SAndroid Build Coastguard Worker       if (current_fit.rms < best_fit.rms) {
143*58b9f456SAndroid Build Coastguard Worker         best_fit = current_fit;
144*58b9f456SAndroid Build Coastguard Worker         best_fit.complexity = fit;
145*58b9f456SAndroid Build Coastguard Worker       }
146*58b9f456SAndroid Build Coastguard Worker     }
147*58b9f456SAndroid Build Coastguard Worker   } else {
148*58b9f456SAndroid Build Coastguard Worker     best_fit = MinimalLeastSq(n, time, FittingCurve(complexity));
149*58b9f456SAndroid Build Coastguard Worker     best_fit.complexity = complexity;
150*58b9f456SAndroid Build Coastguard Worker   }
151*58b9f456SAndroid Build Coastguard Worker 
152*58b9f456SAndroid Build Coastguard Worker   return best_fit;
153*58b9f456SAndroid Build Coastguard Worker }
154*58b9f456SAndroid Build Coastguard Worker 
ComputeBigO(const std::vector<BenchmarkReporter::Run> & reports)155*58b9f456SAndroid Build Coastguard Worker std::vector<BenchmarkReporter::Run> ComputeBigO(
156*58b9f456SAndroid Build Coastguard Worker     const std::vector<BenchmarkReporter::Run>& reports) {
157*58b9f456SAndroid Build Coastguard Worker   typedef BenchmarkReporter::Run Run;
158*58b9f456SAndroid Build Coastguard Worker   std::vector<Run> results;
159*58b9f456SAndroid Build Coastguard Worker 
160*58b9f456SAndroid Build Coastguard Worker   if (reports.size() < 2) return results;
161*58b9f456SAndroid Build Coastguard Worker 
162*58b9f456SAndroid Build Coastguard Worker   // Accumulators.
163*58b9f456SAndroid Build Coastguard Worker   std::vector<int64_t> n;
164*58b9f456SAndroid Build Coastguard Worker   std::vector<double> real_time;
165*58b9f456SAndroid Build Coastguard Worker   std::vector<double> cpu_time;
166*58b9f456SAndroid Build Coastguard Worker 
167*58b9f456SAndroid Build Coastguard Worker   // Populate the accumulators.
168*58b9f456SAndroid Build Coastguard Worker   for (const Run& run : reports) {
169*58b9f456SAndroid Build Coastguard Worker     CHECK_GT(run.complexity_n, 0) << "Did you forget to call SetComplexityN?";
170*58b9f456SAndroid Build Coastguard Worker     n.push_back(run.complexity_n);
171*58b9f456SAndroid Build Coastguard Worker     real_time.push_back(run.real_accumulated_time / run.iterations);
172*58b9f456SAndroid Build Coastguard Worker     cpu_time.push_back(run.cpu_accumulated_time / run.iterations);
173*58b9f456SAndroid Build Coastguard Worker   }
174*58b9f456SAndroid Build Coastguard Worker 
175*58b9f456SAndroid Build Coastguard Worker   LeastSq result_cpu;
176*58b9f456SAndroid Build Coastguard Worker   LeastSq result_real;
177*58b9f456SAndroid Build Coastguard Worker 
178*58b9f456SAndroid Build Coastguard Worker   if (reports[0].complexity == oLambda) {
179*58b9f456SAndroid Build Coastguard Worker     result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda);
180*58b9f456SAndroid Build Coastguard Worker     result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda);
181*58b9f456SAndroid Build Coastguard Worker   } else {
182*58b9f456SAndroid Build Coastguard Worker     result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity);
183*58b9f456SAndroid Build Coastguard Worker     result_real = MinimalLeastSq(n, real_time, result_cpu.complexity);
184*58b9f456SAndroid Build Coastguard Worker   }
185*58b9f456SAndroid Build Coastguard Worker 
186*58b9f456SAndroid Build Coastguard Worker   std::string run_name = reports[0].benchmark_name().substr(
187*58b9f456SAndroid Build Coastguard Worker       0, reports[0].benchmark_name().find('/'));
188*58b9f456SAndroid Build Coastguard Worker 
189*58b9f456SAndroid Build Coastguard Worker   // Get the data from the accumulator to BenchmarkReporter::Run's.
190*58b9f456SAndroid Build Coastguard Worker   Run big_o;
191*58b9f456SAndroid Build Coastguard Worker   big_o.run_name = run_name;
192*58b9f456SAndroid Build Coastguard Worker   big_o.run_type = BenchmarkReporter::Run::RT_Aggregate;
193*58b9f456SAndroid Build Coastguard Worker   big_o.aggregate_name = "BigO";
194*58b9f456SAndroid Build Coastguard Worker   big_o.iterations = 0;
195*58b9f456SAndroid Build Coastguard Worker   big_o.real_accumulated_time = result_real.coef;
196*58b9f456SAndroid Build Coastguard Worker   big_o.cpu_accumulated_time = result_cpu.coef;
197*58b9f456SAndroid Build Coastguard Worker   big_o.report_big_o = true;
198*58b9f456SAndroid Build Coastguard Worker   big_o.complexity = result_cpu.complexity;
199*58b9f456SAndroid Build Coastguard Worker 
200*58b9f456SAndroid Build Coastguard Worker   // All the time results are reported after being multiplied by the
201*58b9f456SAndroid Build Coastguard Worker   // time unit multiplier. But since RMS is a relative quantity it
202*58b9f456SAndroid Build Coastguard Worker   // should not be multiplied at all. So, here, we _divide_ it by the
203*58b9f456SAndroid Build Coastguard Worker   // multiplier so that when it is multiplied later the result is the
204*58b9f456SAndroid Build Coastguard Worker   // correct one.
205*58b9f456SAndroid Build Coastguard Worker   double multiplier = GetTimeUnitMultiplier(reports[0].time_unit);
206*58b9f456SAndroid Build Coastguard Worker 
207*58b9f456SAndroid Build Coastguard Worker   // Only add label to mean/stddev if it is same for all runs
208*58b9f456SAndroid Build Coastguard Worker   Run rms;
209*58b9f456SAndroid Build Coastguard Worker   rms.run_name = run_name;
210*58b9f456SAndroid Build Coastguard Worker   big_o.report_label = reports[0].report_label;
211*58b9f456SAndroid Build Coastguard Worker   rms.run_type = BenchmarkReporter::Run::RT_Aggregate;
212*58b9f456SAndroid Build Coastguard Worker   rms.aggregate_name = "RMS";
213*58b9f456SAndroid Build Coastguard Worker   rms.report_label = big_o.report_label;
214*58b9f456SAndroid Build Coastguard Worker   rms.iterations = 0;
215*58b9f456SAndroid Build Coastguard Worker   rms.real_accumulated_time = result_real.rms / multiplier;
216*58b9f456SAndroid Build Coastguard Worker   rms.cpu_accumulated_time = result_cpu.rms / multiplier;
217*58b9f456SAndroid Build Coastguard Worker   rms.report_rms = true;
218*58b9f456SAndroid Build Coastguard Worker   rms.complexity = result_cpu.complexity;
219*58b9f456SAndroid Build Coastguard Worker   // don't forget to keep the time unit, or we won't be able to
220*58b9f456SAndroid Build Coastguard Worker   // recover the correct value.
221*58b9f456SAndroid Build Coastguard Worker   rms.time_unit = reports[0].time_unit;
222*58b9f456SAndroid Build Coastguard Worker 
223*58b9f456SAndroid Build Coastguard Worker   results.push_back(big_o);
224*58b9f456SAndroid Build Coastguard Worker   results.push_back(rms);
225*58b9f456SAndroid Build Coastguard Worker   return results;
226*58b9f456SAndroid Build Coastguard Worker }
227*58b9f456SAndroid Build Coastguard Worker 
228*58b9f456SAndroid Build Coastguard Worker }  // end namespace benchmark
229