complexity.cc revision 37ab858e4b245a49805b01358655fab069474a7c
1// Copyright 2016 Ismael Jimenez Martinez. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Source project : https://github.com/ismaelJimenez/cpp.leastsq
16// Adapted to be used with google benchmark
17
18#include "benchmark/complexity.h"
19#include "check.h"
20#include <math.h>
21#include <functional>
22
23namespace benchmark {
24
25// Internal function to calculate the different scalability forms
26std::function<double(int)> FittingCurve(BigO complexity) {
27  switch (complexity) {
28    case oN:
29      return [](int n) {return n; };
30    case oNSquared:
31      return [](int n) {return n*n; };
32    case oNCubed:
33      return [](int n) {return n*n*n; };
34    case oLogN:
35      return [](int n) {return log2(n); };
36    case oNLogN:
37      return [](int n) {return n * log2(n); };
38    case o1:
39    default:
40      return [](int) {return 1; };
41  }
42}
43
44// Function to return an string for the calculated complexity
45std::string GetBigOString(BigO complexity) {
46  switch (complexity) {
47    case oN:
48      return "* N";
49    case oNSquared:
50      return "* N**2";
51    case oNCubed:
52      return "* N**3";
53    case oLogN:
54      return "* lgN";
55    case oNLogN:
56      return "* NlgN";
57    case o1:
58      return "* 1";
59    default:
60      return "";
61  }
62}
63
64// Find the coefficient for the high-order term in the running time, by
65// minimizing the sum of squares of relative error, for the fitting curve
66// given by the lambda expresion.
67//   - n             : Vector containing the size of the benchmark tests.
68//   - time          : Vector containing the times for the benchmark tests.
69//   - fitting_curve : lambda expresion (e.g. [](int n) {return n; };).
70
71// For a deeper explanation on the algorithm logic, look the README file at
72// http://github.com/ismaelJimenez/Minimal-Cpp-Least-Squared-Fit
73
74// This interface is currently not used from the oustide, but it has been
75// provided for future upgrades. If in the future it is not needed to support
76// Cxx03, then all the calculations could be upgraded to use lambdas because
77// they are more powerful and provide a cleaner inferface than enumerators,
78// but complete implementation with lambdas will not work for Cxx03
79// (e.g. lack of std::function).
80// In case lambdas are implemented, the interface would be like :
81//   -> Complexity([](int n) {return n;};)
82// and any arbitrary and valid  equation would be allowed, but the option to
83// calculate the best fit to the most common scalability curves will still
84// be kept.
85
86LeastSq CalculateLeastSq(const std::vector<int>& n,
87                         const std::vector<double>& time,
88                         std::function<double(int)> fitting_curve) {
89  double sigma_gn = 0.0;
90  double sigma_gn_squared = 0.0;
91  double sigma_time = 0.0;
92  double sigma_time_gn = 0.0;
93
94  // Calculate least square fitting parameter
95  for (size_t i = 0; i < n.size(); ++i) {
96    double gn_i = fitting_curve(n[i]);
97    sigma_gn += gn_i;
98    sigma_gn_squared += gn_i * gn_i;
99    sigma_time += time[i];
100    sigma_time_gn += time[i] * gn_i;
101  }
102
103  LeastSq result;
104
105  // Calculate complexity.
106  result.coef = sigma_time_gn / sigma_gn_squared;
107
108  // Calculate RMS
109  double rms = 0.0;
110  for (size_t i = 0; i < n.size(); ++i) {
111    double fit = result.coef * fitting_curve(n[i]);
112    rms += pow((time[i] - fit), 2);
113  }
114
115  // Normalized RMS by the mean of the observed values
116  double mean = sigma_time / n.size();
117  result.rms = sqrt(rms / n.size()) / mean;
118
119  return result;
120}
121
122// Find the coefficient for the high-order term in the running time, by
123// minimizing the sum of squares of relative error.
124//   - n          : Vector containing the size of the benchmark tests.
125//   - time       : Vector containing the times for the benchmark tests.
126//   - complexity : If different than oAuto, the fitting curve will stick to
127//                  this one. If it is oAuto, it will be calculated the best
128//                  fitting curve.
129LeastSq MinimalLeastSq(const std::vector<int>& n,
130                       const std::vector<double>& time,
131                       const BigO complexity) {
132  CHECK_EQ(n.size(), time.size());
133  CHECK_GE(n.size(), 2);  // Do not compute fitting curve is less than two benchmark runs are given
134  CHECK_NE(complexity, oNone);
135
136  LeastSq best_fit;
137
138  if(complexity == oAuto) {
139    std::vector<BigO> fit_curves = {
140      oLogN, oN, oNLogN, oNSquared, oNCubed };
141
142    // Take o1 as default best fitting curve
143    best_fit = CalculateLeastSq(n, time, FittingCurve(o1));
144    best_fit.complexity = o1;
145
146    // Compute all possible fitting curves and stick to the best one
147    for (const auto& fit : fit_curves) {
148      LeastSq current_fit = CalculateLeastSq(n, time, FittingCurve(fit));
149      if (current_fit.rms < best_fit.rms) {
150        best_fit = current_fit;
151        best_fit.complexity = fit;
152      }
153    }
154  } else {
155    best_fit = CalculateLeastSq(n, time, FittingCurve(complexity));
156    best_fit.complexity = complexity;
157  }
158
159  return best_fit;
160}
161
162}  // end namespace benchmark
163