xref: /llvm-project/third-party/benchmark/src/complexity.cc (revision a5b797172cc902db166e9a695716fb81405f86e4)
15dda2efdSMircea Trofin // Copyright 2016 Ismael Jimenez Martinez. All rights reserved.
25dda2efdSMircea Trofin //
35dda2efdSMircea Trofin // Licensed under the Apache License, Version 2.0 (the "License");
45dda2efdSMircea Trofin // you may not use this file except in compliance with the License.
55dda2efdSMircea Trofin // You may obtain a copy of the License at
65dda2efdSMircea Trofin //
75dda2efdSMircea Trofin //     http://www.apache.org/licenses/LICENSE-2.0
85dda2efdSMircea Trofin //
95dda2efdSMircea Trofin // Unless required by applicable law or agreed to in writing, software
105dda2efdSMircea Trofin // distributed under the License is distributed on an "AS IS" BASIS,
115dda2efdSMircea Trofin // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
125dda2efdSMircea Trofin // See the License for the specific language governing permissions and
135dda2efdSMircea Trofin // limitations under the License.
145dda2efdSMircea Trofin 
155dda2efdSMircea Trofin // Source project : https://github.com/ismaelJimenez/cpp.leastsq
165dda2efdSMircea Trofin // Adapted to be used with google benchmark
175dda2efdSMircea Trofin 
18a290770fSMircea Trofin #include "complexity.h"
195dda2efdSMircea Trofin 
205dda2efdSMircea Trofin #include <algorithm>
215dda2efdSMircea Trofin #include <cmath>
22a290770fSMircea Trofin 
23a290770fSMircea Trofin #include "benchmark/benchmark.h"
245dda2efdSMircea Trofin #include "check.h"
255dda2efdSMircea Trofin 
265dda2efdSMircea Trofin namespace benchmark {
275dda2efdSMircea Trofin 
285dda2efdSMircea Trofin // Internal function to calculate the different scalability forms
FittingCurve(BigO complexity)295dda2efdSMircea Trofin BigOFunc* FittingCurve(BigO complexity) {
305dda2efdSMircea Trofin   static const double kLog2E = 1.44269504088896340736;
315dda2efdSMircea Trofin   switch (complexity) {
325dda2efdSMircea Trofin     case oN:
335dda2efdSMircea Trofin       return [](IterationCount n) -> double { return static_cast<double>(n); };
345dda2efdSMircea Trofin     case oNSquared:
355dda2efdSMircea Trofin       return [](IterationCount n) -> double { return std::pow(n, 2); };
365dda2efdSMircea Trofin     case oNCubed:
375dda2efdSMircea Trofin       return [](IterationCount n) -> double { return std::pow(n, 3); };
385dda2efdSMircea Trofin     case oLogN:
395dda2efdSMircea Trofin       /* Note: can't use log2 because Android's GNU STL lacks it */
40*a5b79717SMircea Trofin       return [](IterationCount n) {
41*a5b79717SMircea Trofin         return kLog2E * std::log(static_cast<double>(n));
42*a5b79717SMircea Trofin       };
435dda2efdSMircea Trofin     case oNLogN:
445dda2efdSMircea Trofin       /* Note: can't use log2 because Android's GNU STL lacks it */
455dda2efdSMircea Trofin       return [](IterationCount n) {
46*a5b79717SMircea Trofin         return kLog2E * static_cast<double>(n) *
47*a5b79717SMircea Trofin                std::log(static_cast<double>(n));
485dda2efdSMircea Trofin       };
495dda2efdSMircea Trofin     case o1:
505dda2efdSMircea Trofin     default:
515dda2efdSMircea Trofin       return [](IterationCount) { return 1.0; };
525dda2efdSMircea Trofin   }
535dda2efdSMircea Trofin }
545dda2efdSMircea Trofin 
555dda2efdSMircea Trofin // Function to return an string for the calculated complexity
GetBigOString(BigO complexity)565dda2efdSMircea Trofin std::string GetBigOString(BigO complexity) {
575dda2efdSMircea Trofin   switch (complexity) {
585dda2efdSMircea Trofin     case oN:
595dda2efdSMircea Trofin       return "N";
605dda2efdSMircea Trofin     case oNSquared:
615dda2efdSMircea Trofin       return "N^2";
625dda2efdSMircea Trofin     case oNCubed:
635dda2efdSMircea Trofin       return "N^3";
645dda2efdSMircea Trofin     case oLogN:
655dda2efdSMircea Trofin       return "lgN";
665dda2efdSMircea Trofin     case oNLogN:
675dda2efdSMircea Trofin       return "NlgN";
685dda2efdSMircea Trofin     case o1:
695dda2efdSMircea Trofin       return "(1)";
705dda2efdSMircea Trofin     default:
715dda2efdSMircea Trofin       return "f(N)";
725dda2efdSMircea Trofin   }
735dda2efdSMircea Trofin }
745dda2efdSMircea Trofin 
755dda2efdSMircea Trofin // Find the coefficient for the high-order term in the running time, by
765dda2efdSMircea Trofin // minimizing the sum of squares of relative error, for the fitting curve
775dda2efdSMircea Trofin // given by the lambda expression.
785dda2efdSMircea Trofin //   - n             : Vector containing the size of the benchmark tests.
795dda2efdSMircea Trofin //   - time          : Vector containing the times for the benchmark tests.
80*a5b79717SMircea Trofin //   - fitting_curve : lambda expression (e.g. [](ComplexityN n) {return n; };).
815dda2efdSMircea Trofin 
825dda2efdSMircea Trofin // For a deeper explanation on the algorithm logic, please refer to
835dda2efdSMircea Trofin // https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics
845dda2efdSMircea Trofin 
MinimalLeastSq(const std::vector<ComplexityN> & n,const std::vector<double> & time,BigOFunc * fitting_curve)85*a5b79717SMircea Trofin LeastSq MinimalLeastSq(const std::vector<ComplexityN>& n,
865dda2efdSMircea Trofin                        const std::vector<double>& time,
875dda2efdSMircea Trofin                        BigOFunc* fitting_curve) {
885dda2efdSMircea Trofin   double sigma_gn_squared = 0.0;
895dda2efdSMircea Trofin   double sigma_time = 0.0;
905dda2efdSMircea Trofin   double sigma_time_gn = 0.0;
915dda2efdSMircea Trofin 
925dda2efdSMircea Trofin   // Calculate least square fitting parameter
935dda2efdSMircea Trofin   for (size_t i = 0; i < n.size(); ++i) {
945dda2efdSMircea Trofin     double gn_i = fitting_curve(n[i]);
955dda2efdSMircea Trofin     sigma_gn_squared += gn_i * gn_i;
965dda2efdSMircea Trofin     sigma_time += time[i];
975dda2efdSMircea Trofin     sigma_time_gn += time[i] * gn_i;
985dda2efdSMircea Trofin   }
995dda2efdSMircea Trofin 
1005dda2efdSMircea Trofin   LeastSq result;
1015dda2efdSMircea Trofin   result.complexity = oLambda;
1025dda2efdSMircea Trofin 
1035dda2efdSMircea Trofin   // Calculate complexity.
1045dda2efdSMircea Trofin   result.coef = sigma_time_gn / sigma_gn_squared;
1055dda2efdSMircea Trofin 
1065dda2efdSMircea Trofin   // Calculate RMS
1075dda2efdSMircea Trofin   double rms = 0.0;
1085dda2efdSMircea Trofin   for (size_t i = 0; i < n.size(); ++i) {
1095dda2efdSMircea Trofin     double fit = result.coef * fitting_curve(n[i]);
110*a5b79717SMircea Trofin     rms += std::pow((time[i] - fit), 2);
1115dda2efdSMircea Trofin   }
1125dda2efdSMircea Trofin 
1135dda2efdSMircea Trofin   // Normalized RMS by the mean of the observed values
114*a5b79717SMircea Trofin   double mean = sigma_time / static_cast<double>(n.size());
115*a5b79717SMircea Trofin   result.rms = std::sqrt(rms / static_cast<double>(n.size())) / mean;
1165dda2efdSMircea Trofin 
1175dda2efdSMircea Trofin   return result;
1185dda2efdSMircea Trofin }
1195dda2efdSMircea Trofin 
1205dda2efdSMircea Trofin // Find the coefficient for the high-order term in the running time, by
1215dda2efdSMircea Trofin // minimizing the sum of squares of relative error.
1225dda2efdSMircea Trofin //   - n          : Vector containing the size of the benchmark tests.
1235dda2efdSMircea Trofin //   - time       : Vector containing the times for the benchmark tests.
1245dda2efdSMircea Trofin //   - complexity : If different than oAuto, the fitting curve will stick to
1255dda2efdSMircea Trofin //                  this one. If it is oAuto, it will be calculated the best
1265dda2efdSMircea Trofin //                  fitting curve.
MinimalLeastSq(const std::vector<ComplexityN> & n,const std::vector<double> & time,const BigO complexity)127*a5b79717SMircea Trofin LeastSq MinimalLeastSq(const std::vector<ComplexityN>& n,
1285dda2efdSMircea Trofin                        const std::vector<double>& time, const BigO complexity) {
129a290770fSMircea Trofin   BM_CHECK_EQ(n.size(), time.size());
130a290770fSMircea Trofin   BM_CHECK_GE(n.size(), 2);  // Do not compute fitting curve is less than two
1315dda2efdSMircea Trofin                              // benchmark runs are given
132a290770fSMircea Trofin   BM_CHECK_NE(complexity, oNone);
1335dda2efdSMircea Trofin 
1345dda2efdSMircea Trofin   LeastSq best_fit;
1355dda2efdSMircea Trofin 
1365dda2efdSMircea Trofin   if (complexity == oAuto) {
1375dda2efdSMircea Trofin     std::vector<BigO> fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed};
1385dda2efdSMircea Trofin 
1395dda2efdSMircea Trofin     // Take o1 as default best fitting curve
1405dda2efdSMircea Trofin     best_fit = MinimalLeastSq(n, time, FittingCurve(o1));
1415dda2efdSMircea Trofin     best_fit.complexity = o1;
1425dda2efdSMircea Trofin 
1435dda2efdSMircea Trofin     // Compute all possible fitting curves and stick to the best one
1445dda2efdSMircea Trofin     for (const auto& fit : fit_curves) {
1455dda2efdSMircea Trofin       LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit));
1465dda2efdSMircea Trofin       if (current_fit.rms < best_fit.rms) {
1475dda2efdSMircea Trofin         best_fit = current_fit;
1485dda2efdSMircea Trofin         best_fit.complexity = fit;
1495dda2efdSMircea Trofin       }
1505dda2efdSMircea Trofin     }
1515dda2efdSMircea Trofin   } else {
1525dda2efdSMircea Trofin     best_fit = MinimalLeastSq(n, time, FittingCurve(complexity));
1535dda2efdSMircea Trofin     best_fit.complexity = complexity;
1545dda2efdSMircea Trofin   }
1555dda2efdSMircea Trofin 
1565dda2efdSMircea Trofin   return best_fit;
1575dda2efdSMircea Trofin }
1585dda2efdSMircea Trofin 
ComputeBigO(const std::vector<BenchmarkReporter::Run> & reports)1595dda2efdSMircea Trofin std::vector<BenchmarkReporter::Run> ComputeBigO(
1605dda2efdSMircea Trofin     const std::vector<BenchmarkReporter::Run>& reports) {
1615dda2efdSMircea Trofin   typedef BenchmarkReporter::Run Run;
1625dda2efdSMircea Trofin   std::vector<Run> results;
1635dda2efdSMircea Trofin 
1645dda2efdSMircea Trofin   if (reports.size() < 2) return results;
1655dda2efdSMircea Trofin 
1665dda2efdSMircea Trofin   // Accumulators.
167*a5b79717SMircea Trofin   std::vector<ComplexityN> n;
1685dda2efdSMircea Trofin   std::vector<double> real_time;
1695dda2efdSMircea Trofin   std::vector<double> cpu_time;
1705dda2efdSMircea Trofin 
1715dda2efdSMircea Trofin   // Populate the accumulators.
1725dda2efdSMircea Trofin   for (const Run& run : reports) {
173a290770fSMircea Trofin     BM_CHECK_GT(run.complexity_n, 0)
174a290770fSMircea Trofin         << "Did you forget to call SetComplexityN?";
1755dda2efdSMircea Trofin     n.push_back(run.complexity_n);
176*a5b79717SMircea Trofin     real_time.push_back(run.real_accumulated_time /
177*a5b79717SMircea Trofin                         static_cast<double>(run.iterations));
178*a5b79717SMircea Trofin     cpu_time.push_back(run.cpu_accumulated_time /
179*a5b79717SMircea Trofin                        static_cast<double>(run.iterations));
1805dda2efdSMircea Trofin   }
1815dda2efdSMircea Trofin 
1825dda2efdSMircea Trofin   LeastSq result_cpu;
1835dda2efdSMircea Trofin   LeastSq result_real;
1845dda2efdSMircea Trofin 
1855dda2efdSMircea Trofin   if (reports[0].complexity == oLambda) {
1865dda2efdSMircea Trofin     result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda);
1875dda2efdSMircea Trofin     result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda);
1885dda2efdSMircea Trofin   } else {
189*a5b79717SMircea Trofin     const BigO* InitialBigO = &reports[0].complexity;
190*a5b79717SMircea Trofin     const bool use_real_time_for_initial_big_o =
191*a5b79717SMircea Trofin         reports[0].use_real_time_for_initial_big_o;
192*a5b79717SMircea Trofin     if (use_real_time_for_initial_big_o) {
193*a5b79717SMircea Trofin       result_real = MinimalLeastSq(n, real_time, *InitialBigO);
194*a5b79717SMircea Trofin       InitialBigO = &result_real.complexity;
195*a5b79717SMircea Trofin       // The Big-O complexity for CPU time must have the same Big-O function!
196*a5b79717SMircea Trofin     }
197*a5b79717SMircea Trofin     result_cpu = MinimalLeastSq(n, cpu_time, *InitialBigO);
198*a5b79717SMircea Trofin     InitialBigO = &result_cpu.complexity;
199*a5b79717SMircea Trofin     if (!use_real_time_for_initial_big_o) {
200*a5b79717SMircea Trofin       result_real = MinimalLeastSq(n, real_time, *InitialBigO);
201*a5b79717SMircea Trofin     }
2025dda2efdSMircea Trofin   }
2035dda2efdSMircea Trofin 
2045dda2efdSMircea Trofin   // Drop the 'args' when reporting complexity.
2055dda2efdSMircea Trofin   auto run_name = reports[0].run_name;
2065dda2efdSMircea Trofin   run_name.args.clear();
2075dda2efdSMircea Trofin 
2085dda2efdSMircea Trofin   // Get the data from the accumulator to BenchmarkReporter::Run's.
2095dda2efdSMircea Trofin   Run big_o;
2105dda2efdSMircea Trofin   big_o.run_name = run_name;
2115dda2efdSMircea Trofin   big_o.family_index = reports[0].family_index;
2125dda2efdSMircea Trofin   big_o.per_family_instance_index = reports[0].per_family_instance_index;
2135dda2efdSMircea Trofin   big_o.run_type = BenchmarkReporter::Run::RT_Aggregate;
2145dda2efdSMircea Trofin   big_o.repetitions = reports[0].repetitions;
2155dda2efdSMircea Trofin   big_o.repetition_index = Run::no_repetition_index;
2165dda2efdSMircea Trofin   big_o.threads = reports[0].threads;
2175dda2efdSMircea Trofin   big_o.aggregate_name = "BigO";
218a290770fSMircea Trofin   big_o.aggregate_unit = StatisticUnit::kTime;
2195dda2efdSMircea Trofin   big_o.report_label = reports[0].report_label;
2205dda2efdSMircea Trofin   big_o.iterations = 0;
2215dda2efdSMircea Trofin   big_o.real_accumulated_time = result_real.coef;
2225dda2efdSMircea Trofin   big_o.cpu_accumulated_time = result_cpu.coef;
2235dda2efdSMircea Trofin   big_o.report_big_o = true;
2245dda2efdSMircea Trofin   big_o.complexity = result_cpu.complexity;
2255dda2efdSMircea Trofin 
2265dda2efdSMircea Trofin   // All the time results are reported after being multiplied by the
2275dda2efdSMircea Trofin   // time unit multiplier. But since RMS is a relative quantity it
2285dda2efdSMircea Trofin   // should not be multiplied at all. So, here, we _divide_ it by the
2295dda2efdSMircea Trofin   // multiplier so that when it is multiplied later the result is the
2305dda2efdSMircea Trofin   // correct one.
2315dda2efdSMircea Trofin   double multiplier = GetTimeUnitMultiplier(reports[0].time_unit);
2325dda2efdSMircea Trofin 
2335dda2efdSMircea Trofin   // Only add label to mean/stddev if it is same for all runs
2345dda2efdSMircea Trofin   Run rms;
2355dda2efdSMircea Trofin   rms.run_name = run_name;
2365dda2efdSMircea Trofin   rms.family_index = reports[0].family_index;
2375dda2efdSMircea Trofin   rms.per_family_instance_index = reports[0].per_family_instance_index;
2385dda2efdSMircea Trofin   rms.run_type = BenchmarkReporter::Run::RT_Aggregate;
2395dda2efdSMircea Trofin   rms.aggregate_name = "RMS";
240a290770fSMircea Trofin   rms.aggregate_unit = StatisticUnit::kPercentage;
2415dda2efdSMircea Trofin   rms.report_label = big_o.report_label;
2425dda2efdSMircea Trofin   rms.iterations = 0;
2435dda2efdSMircea Trofin   rms.repetition_index = Run::no_repetition_index;
2445dda2efdSMircea Trofin   rms.repetitions = reports[0].repetitions;
2455dda2efdSMircea Trofin   rms.threads = reports[0].threads;
2465dda2efdSMircea Trofin   rms.real_accumulated_time = result_real.rms / multiplier;
2475dda2efdSMircea Trofin   rms.cpu_accumulated_time = result_cpu.rms / multiplier;
2485dda2efdSMircea Trofin   rms.report_rms = true;
2495dda2efdSMircea Trofin   rms.complexity = result_cpu.complexity;
2505dda2efdSMircea Trofin   // don't forget to keep the time unit, or we won't be able to
2515dda2efdSMircea Trofin   // recover the correct value.
2525dda2efdSMircea Trofin   rms.time_unit = reports[0].time_unit;
2535dda2efdSMircea Trofin 
2545dda2efdSMircea Trofin   results.push_back(big_o);
2555dda2efdSMircea Trofin   results.push_back(rms);
2565dda2efdSMircea Trofin   return results;
2575dda2efdSMircea Trofin }
2585dda2efdSMircea Trofin 
2595dda2efdSMircea Trofin }  // end namespace benchmark
260