complexity.cc 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. // Source project : https://github.com/ismaelJimenez/cpp.leastsq
  15. // Adapted to be used with google benchmark
  16. #include "complexity.h"
  17. #include <algorithm>
  18. #include <cmath>
  19. #include "benchmark/benchmark.h"
  20. #include "check.h"
  21. namespace benchmark {
  22. // Internal function to calculate the different scalability forms
  23. BigOFunc* FittingCurve(BigO complexity) {
  24. static const double kLog2E = 1.44269504088896340736;
  25. switch (complexity) {
  26. case oN:
  27. return [](IterationCount n) -> double { return static_cast<double>(n); };
  28. case oNSquared:
  29. return [](IterationCount n) -> double { return std::pow(n, 2); };
  30. case oNCubed:
  31. return [](IterationCount n) -> double { return std::pow(n, 3); };
  32. case oLogN:
  33. /* Note: can't use log2 because Android's GNU STL lacks it */
  34. return
  35. [](IterationCount n) { return kLog2E * log(static_cast<double>(n)); };
  36. case oNLogN:
  37. /* Note: can't use log2 because Android's GNU STL lacks it */
  38. return [](IterationCount n) {
  39. return kLog2E * n * log(static_cast<double>(n));
  40. };
  41. case o1:
  42. default:
  43. return [](IterationCount) { return 1.0; };
  44. }
  45. }
  46. // Function to return an string for the calculated complexity
  47. std::string GetBigOString(BigO complexity) {
  48. switch (complexity) {
  49. case oN:
  50. return "N";
  51. case oNSquared:
  52. return "N^2";
  53. case oNCubed:
  54. return "N^3";
  55. case oLogN:
  56. return "lgN";
  57. case oNLogN:
  58. return "NlgN";
  59. case o1:
  60. return "(1)";
  61. default:
  62. return "f(N)";
  63. }
  64. }
  65. // Find the coefficient for the high-order term in the running time, by
  66. // minimizing the sum of squares of relative error, for the fitting curve
  67. // given by the lambda expression.
  68. // - n : Vector containing the size of the benchmark tests.
  69. // - time : Vector containing the times for the benchmark tests.
  70. // - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };).
  71. // For a deeper explanation on the algorithm logic, please refer to
  72. // https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics
  73. LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
  74. const std::vector<double>& time,
  75. BigOFunc* fitting_curve) {
  76. double sigma_gn_squared = 0.0;
  77. double sigma_time = 0.0;
  78. double sigma_time_gn = 0.0;
  79. // Calculate least square fitting parameter
  80. for (size_t i = 0; i < n.size(); ++i) {
  81. double gn_i = fitting_curve(n[i]);
  82. sigma_gn_squared += gn_i * gn_i;
  83. sigma_time += time[i];
  84. sigma_time_gn += time[i] * gn_i;
  85. }
  86. LeastSq result;
  87. result.complexity = oLambda;
  88. // Calculate complexity.
  89. result.coef = sigma_time_gn / sigma_gn_squared;
  90. // Calculate RMS
  91. double rms = 0.0;
  92. for (size_t i = 0; i < n.size(); ++i) {
  93. double fit = result.coef * fitting_curve(n[i]);
  94. rms += pow((time[i] - fit), 2);
  95. }
  96. // Normalized RMS by the mean of the observed values
  97. double mean = sigma_time / n.size();
  98. result.rms = sqrt(rms / n.size()) / mean;
  99. return result;
  100. }
  101. // Find the coefficient for the high-order term in the running time, by
  102. // minimizing the sum of squares of relative error.
  103. // - n : Vector containing the size of the benchmark tests.
  104. // - time : Vector containing the times for the benchmark tests.
  105. // - complexity : If different than oAuto, the fitting curve will stick to
  106. // this one. If it is oAuto, it will be calculated the best
  107. // fitting curve.
  108. LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
  109. const std::vector<double>& time, const BigO complexity) {
  110. BM_CHECK_EQ(n.size(), time.size());
  111. BM_CHECK_GE(n.size(), 2); // Do not compute fitting curve is less than two
  112. // benchmark runs are given
  113. BM_CHECK_NE(complexity, oNone);
  114. LeastSq best_fit;
  115. if (complexity == oAuto) {
  116. std::vector<BigO> fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed};
  117. // Take o1 as default best fitting curve
  118. best_fit = MinimalLeastSq(n, time, FittingCurve(o1));
  119. best_fit.complexity = o1;
  120. // Compute all possible fitting curves and stick to the best one
  121. for (const auto& fit : fit_curves) {
  122. LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit));
  123. if (current_fit.rms < best_fit.rms) {
  124. best_fit = current_fit;
  125. best_fit.complexity = fit;
  126. }
  127. }
  128. } else {
  129. best_fit = MinimalLeastSq(n, time, FittingCurve(complexity));
  130. best_fit.complexity = complexity;
  131. }
  132. return best_fit;
  133. }
  134. std::vector<BenchmarkReporter::Run> ComputeBigO(
  135. const std::vector<BenchmarkReporter::Run>& reports) {
  136. typedef BenchmarkReporter::Run Run;
  137. std::vector<Run> results;
  138. if (reports.size() < 2) return results;
  139. // Accumulators.
  140. std::vector<int64_t> n;
  141. std::vector<double> real_time;
  142. std::vector<double> cpu_time;
  143. // Populate the accumulators.
  144. for (const Run& run : reports) {
  145. BM_CHECK_GT(run.complexity_n, 0)
  146. << "Did you forget to call SetComplexityN?";
  147. n.push_back(run.complexity_n);
  148. real_time.push_back(run.real_accumulated_time / run.iterations);
  149. cpu_time.push_back(run.cpu_accumulated_time / run.iterations);
  150. }
  151. LeastSq result_cpu;
  152. LeastSq result_real;
  153. if (reports[0].complexity == oLambda) {
  154. result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda);
  155. result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda);
  156. } else {
  157. result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity);
  158. result_real = MinimalLeastSq(n, real_time, result_cpu.complexity);
  159. }
  160. // Drop the 'args' when reporting complexity.
  161. auto run_name = reports[0].run_name;
  162. run_name.args.clear();
  163. // Get the data from the accumulator to BenchmarkReporter::Run's.
  164. Run big_o;
  165. big_o.run_name = run_name;
  166. big_o.family_index = reports[0].family_index;
  167. big_o.per_family_instance_index = reports[0].per_family_instance_index;
  168. big_o.run_type = BenchmarkReporter::Run::RT_Aggregate;
  169. big_o.repetitions = reports[0].repetitions;
  170. big_o.repetition_index = Run::no_repetition_index;
  171. big_o.threads = reports[0].threads;
  172. big_o.aggregate_name = "BigO";
  173. big_o.aggregate_unit = StatisticUnit::kTime;
  174. big_o.report_label = reports[0].report_label;
  175. big_o.iterations = 0;
  176. big_o.real_accumulated_time = result_real.coef;
  177. big_o.cpu_accumulated_time = result_cpu.coef;
  178. big_o.report_big_o = true;
  179. big_o.complexity = result_cpu.complexity;
  180. // All the time results are reported after being multiplied by the
  181. // time unit multiplier. But since RMS is a relative quantity it
  182. // should not be multiplied at all. So, here, we _divide_ it by the
  183. // multiplier so that when it is multiplied later the result is the
  184. // correct one.
  185. double multiplier = GetTimeUnitMultiplier(reports[0].time_unit);
  186. // Only add label to mean/stddev if it is same for all runs
  187. Run rms;
  188. rms.run_name = run_name;
  189. rms.family_index = reports[0].family_index;
  190. rms.per_family_instance_index = reports[0].per_family_instance_index;
  191. rms.run_type = BenchmarkReporter::Run::RT_Aggregate;
  192. rms.aggregate_name = "RMS";
  193. rms.aggregate_unit = StatisticUnit::kPercentage;
  194. rms.report_label = big_o.report_label;
  195. rms.iterations = 0;
  196. rms.repetition_index = Run::no_repetition_index;
  197. rms.repetitions = reports[0].repetitions;
  198. rms.threads = reports[0].threads;
  199. rms.real_accumulated_time = result_real.rms / multiplier;
  200. rms.cpu_accumulated_time = result_cpu.rms / multiplier;
  201. rms.report_rms = true;
  202. rms.complexity = result_cpu.complexity;
  203. // don't forget to keep the time unit, or we won't be able to
  204. // recover the correct value.
  205. rms.time_unit = reports[0].time_unit;
  206. results.push_back(big_o);
  207. results.push_back(rms);
  208. return results;
  209. }
  210. } // end namespace benchmark