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