2016-05-20 14:49:39 +00:00
|
|
|
// Copyright 2016 Ismael Jimenez Martinez. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
// Source project : https://github.com/ismaelJimenez/cpp.leastsq
|
2016-05-21 09:51:42 +00:00
|
|
|
// Adapted to be used with google benchmark
|
2016-05-20 14:49:39 +00:00
|
|
|
|
2017-07-04 22:31:47 +00:00
|
|
|
#include "benchmark/benchmark.h"
|
2016-05-26 20:39:17 +00:00
|
|
|
|
2016-06-02 20:01:31 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cmath>
|
2016-05-23 18:12:54 +00:00
|
|
|
#include "check.h"
|
2016-06-02 20:01:31 +00:00
|
|
|
#include "complexity.h"
|
2016-05-20 14:49:39 +00:00
|
|
|
|
2016-05-25 20:57:52 +00:00
|
|
|
namespace benchmark {
|
2016-06-01 21:08:01 +00:00
|
|
|
|
2016-05-20 14:49:39 +00:00
|
|
|
// Internal function to calculate the different scalability forms
|
2016-06-01 21:08:01 +00:00
|
|
|
BigOFunc* FittingCurve(BigO complexity) {
|
2018-06-05 10:36:26 +00:00
|
|
|
static const double kLog2E = 1.44269504088896340736;
|
2016-05-25 20:26:57 +00:00
|
|
|
switch (complexity) {
|
2016-06-02 20:01:31 +00:00
|
|
|
case oN:
|
2018-04-23 10:58:02 +00:00
|
|
|
return [](int64_t n) -> double { return static_cast<double>(n); };
|
2016-06-02 20:01:31 +00:00
|
|
|
case oNSquared:
|
2018-04-12 14:40:24 +00:00
|
|
|
return [](int64_t n) -> double { return std::pow(n, 2); };
|
2016-06-02 20:01:31 +00:00
|
|
|
case oNCubed:
|
2018-04-12 14:40:24 +00:00
|
|
|
return [](int64_t n) -> double { return std::pow(n, 3); };
|
2016-06-02 20:01:31 +00:00
|
|
|
case oLogN:
|
2018-06-05 10:36:26 +00:00
|
|
|
/* Note: can't use log2 because Android's GNU STL lacks it */
|
2018-07-09 10:45:10 +00:00
|
|
|
return [](int64_t n) { return kLog2E * log(static_cast<double>(n)); };
|
2016-06-02 20:01:31 +00:00
|
|
|
case oNLogN:
|
2018-06-05 10:36:26 +00:00
|
|
|
/* Note: can't use log2 because Android's GNU STL lacks it */
|
2018-07-09 10:45:10 +00:00
|
|
|
return [](int64_t n) { return kLog2E * n * log(static_cast<double>(n)); };
|
2016-06-02 20:01:31 +00:00
|
|
|
case o1:
|
|
|
|
default:
|
2018-04-12 14:40:24 +00:00
|
|
|
return [](int64_t) { return 1.0; };
|
2016-05-25 20:26:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-25 21:33:25 +00:00
|
|
|
// Function to return an string for the calculated complexity
|
2016-05-25 20:57:52 +00:00
|
|
|
std::string GetBigOString(BigO complexity) {
|
2016-05-23 18:40:41 +00:00
|
|
|
switch (complexity) {
|
2016-06-02 20:01:31 +00:00
|
|
|
case oN:
|
|
|
|
return "N";
|
|
|
|
case oNSquared:
|
|
|
|
return "N^2";
|
|
|
|
case oNCubed:
|
|
|
|
return "N^3";
|
|
|
|
case oLogN:
|
|
|
|
return "lgN";
|
|
|
|
case oNLogN:
|
|
|
|
return "NlgN";
|
|
|
|
case o1:
|
|
|
|
return "(1)";
|
|
|
|
default:
|
|
|
|
return "f(N)";
|
2016-05-23 18:40:41 +00:00
|
|
|
}
|
2016-05-20 14:49:39 +00:00
|
|
|
}
|
|
|
|
|
2016-06-02 20:01:31 +00:00
|
|
|
// Find the coefficient for the high-order term in the running time, by
|
|
|
|
// minimizing the sum of squares of relative error, for the fitting curve
|
2018-03-06 11:44:25 +00:00
|
|
|
// given by the lambda expression.
|
2016-05-25 20:26:57 +00:00
|
|
|
// - n : Vector containing the size of the benchmark tests.
|
|
|
|
// - time : Vector containing the times for the benchmark tests.
|
2018-04-12 14:40:24 +00:00
|
|
|
// - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };).
|
2016-05-25 21:33:25 +00:00
|
|
|
|
2016-05-24 20:25:59 +00:00
|
|
|
// For a deeper explanation on the algorithm logic, look the README file at
|
|
|
|
// http://github.com/ismaelJimenez/Minimal-Cpp-Least-Squared-Fit
|
2016-05-20 14:49:39 +00:00
|
|
|
|
2018-04-12 14:40:24 +00:00
|
|
|
LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
|
2016-06-01 21:08:01 +00:00
|
|
|
const std::vector<double>& time,
|
|
|
|
BigOFunc* fitting_curve) {
|
2016-05-26 17:44:11 +00:00
|
|
|
double sigma_gn = 0.0;
|
|
|
|
double sigma_gn_squared = 0.0;
|
|
|
|
double sigma_time = 0.0;
|
|
|
|
double sigma_time_gn = 0.0;
|
2016-05-23 18:40:41 +00:00
|
|
|
|
|
|
|
// Calculate least square fitting parameter
|
|
|
|
for (size_t i = 0; i < n.size(); ++i) {
|
2016-05-25 20:26:57 +00:00
|
|
|
double gn_i = fitting_curve(n[i]);
|
2016-05-23 18:40:41 +00:00
|
|
|
sigma_gn += gn_i;
|
|
|
|
sigma_gn_squared += gn_i * gn_i;
|
|
|
|
sigma_time += time[i];
|
|
|
|
sigma_time_gn += time[i] * gn_i;
|
|
|
|
}
|
|
|
|
|
|
|
|
LeastSq result;
|
2016-06-01 21:08:01 +00:00
|
|
|
result.complexity = oLambda;
|
2016-05-23 18:40:41 +00:00
|
|
|
|
2016-05-24 20:25:59 +00:00
|
|
|
// Calculate complexity.
|
2016-05-25 20:26:57 +00:00
|
|
|
result.coef = sigma_time_gn / sigma_gn_squared;
|
2016-05-23 18:40:41 +00:00
|
|
|
|
|
|
|
// Calculate RMS
|
2016-05-26 17:44:11 +00:00
|
|
|
double rms = 0.0;
|
2016-05-23 18:40:41 +00:00
|
|
|
for (size_t i = 0; i < n.size(); ++i) {
|
2016-05-25 20:26:57 +00:00
|
|
|
double fit = result.coef * fitting_curve(n[i]);
|
2016-05-23 18:40:41 +00:00
|
|
|
rms += pow((time[i] - fit), 2);
|
|
|
|
}
|
|
|
|
|
2016-05-24 20:25:59 +00:00
|
|
|
// Normalized RMS by the mean of the observed values
|
2016-05-25 20:26:57 +00:00
|
|
|
double mean = sigma_time / n.size();
|
2016-05-24 20:25:59 +00:00
|
|
|
result.rms = sqrt(rms / n.size()) / mean;
|
2016-05-23 18:40:41 +00:00
|
|
|
|
|
|
|
return result;
|
2016-05-20 14:49:39 +00:00
|
|
|
}
|
|
|
|
|
2016-05-24 20:25:59 +00:00
|
|
|
// Find the coefficient for the high-order term in the running time, by
|
|
|
|
// minimizing the sum of squares of relative error.
|
2016-05-23 18:12:54 +00:00
|
|
|
// - n : Vector containing the size of the benchmark tests.
|
|
|
|
// - time : Vector containing the times for the benchmark tests.
|
2016-05-24 20:25:59 +00:00
|
|
|
// - complexity : If different than oAuto, the fitting curve will stick to
|
|
|
|
// this one. If it is oAuto, it will be calculated the best
|
|
|
|
// fitting curve.
|
2018-04-12 14:40:24 +00:00
|
|
|
LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
|
2016-10-07 18:35:03 +00:00
|
|
|
const std::vector<double>& time, const BigO complexity) {
|
2016-05-23 18:40:41 +00:00
|
|
|
CHECK_EQ(n.size(), time.size());
|
2016-06-02 20:01:31 +00:00
|
|
|
CHECK_GE(n.size(), 2); // Do not compute fitting curve is less than two
|
|
|
|
// benchmark runs are given
|
2016-05-25 20:57:52 +00:00
|
|
|
CHECK_NE(complexity, oNone);
|
2016-05-23 18:40:41 +00:00
|
|
|
|
2016-05-25 20:26:57 +00:00
|
|
|
LeastSq best_fit;
|
|
|
|
|
2016-06-02 20:01:31 +00:00
|
|
|
if (complexity == oAuto) {
|
|
|
|
std::vector<BigO> fit_curves = {oLogN, oN, oNLogN, oNSquared, oNCubed};
|
2016-05-23 18:40:41 +00:00
|
|
|
|
2016-05-24 20:25:59 +00:00
|
|
|
// Take o1 as default best fitting curve
|
2016-06-01 21:08:01 +00:00
|
|
|
best_fit = MinimalLeastSq(n, time, FittingCurve(o1));
|
2016-05-25 20:57:52 +00:00
|
|
|
best_fit.complexity = o1;
|
2016-05-23 18:40:41 +00:00
|
|
|
|
|
|
|
// Compute all possible fitting curves and stick to the best one
|
|
|
|
for (const auto& fit : fit_curves) {
|
2016-06-01 21:08:01 +00:00
|
|
|
LeastSq current_fit = MinimalLeastSq(n, time, FittingCurve(fit));
|
2016-05-24 20:25:59 +00:00
|
|
|
if (current_fit.rms < best_fit.rms) {
|
2016-05-23 18:40:41 +00:00
|
|
|
best_fit = current_fit;
|
2016-05-25 20:26:57 +00:00
|
|
|
best_fit.complexity = fit;
|
2016-05-24 20:25:59 +00:00
|
|
|
}
|
2016-05-23 18:40:41 +00:00
|
|
|
}
|
2016-05-25 20:26:57 +00:00
|
|
|
} else {
|
2016-06-01 21:08:01 +00:00
|
|
|
best_fit = MinimalLeastSq(n, time, FittingCurve(complexity));
|
2016-05-25 20:26:57 +00:00
|
|
|
best_fit.complexity = complexity;
|
2016-05-23 18:40:41 +00:00
|
|
|
}
|
2016-05-24 20:25:59 +00:00
|
|
|
|
2016-05-25 20:26:57 +00:00
|
|
|
return best_fit;
|
2016-05-24 20:25:59 +00:00
|
|
|
}
|
2016-05-25 20:57:52 +00:00
|
|
|
|
2016-05-27 22:45:25 +00:00
|
|
|
std::vector<BenchmarkReporter::Run> ComputeBigO(
|
2016-06-02 20:01:31 +00:00
|
|
|
const std::vector<BenchmarkReporter::Run>& reports) {
|
2016-05-27 22:45:25 +00:00
|
|
|
typedef BenchmarkReporter::Run Run;
|
|
|
|
std::vector<Run> results;
|
|
|
|
|
|
|
|
if (reports.size() < 2) return results;
|
|
|
|
|
|
|
|
// Accumulators.
|
2018-04-12 14:40:24 +00:00
|
|
|
std::vector<int64_t> n;
|
2016-05-27 22:45:25 +00:00
|
|
|
std::vector<double> real_time;
|
|
|
|
std::vector<double> cpu_time;
|
|
|
|
|
|
|
|
// Populate the accumulators.
|
|
|
|
for (const Run& run : reports) {
|
2016-06-27 18:26:23 +00:00
|
|
|
CHECK_GT(run.complexity_n, 0) << "Did you forget to call SetComplexityN?";
|
2016-05-27 22:45:25 +00:00
|
|
|
n.push_back(run.complexity_n);
|
2016-06-02 20:01:31 +00:00
|
|
|
real_time.push_back(run.real_accumulated_time / run.iterations);
|
|
|
|
cpu_time.push_back(run.cpu_accumulated_time / run.iterations);
|
2016-05-27 22:45:25 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 21:08:01 +00:00
|
|
|
LeastSq result_cpu;
|
|
|
|
LeastSq result_real;
|
2016-05-27 22:45:25 +00:00
|
|
|
|
2016-06-02 20:23:39 +00:00
|
|
|
if (reports[0].complexity == oLambda) {
|
2016-06-01 21:08:01 +00:00
|
|
|
result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity_lambda);
|
|
|
|
result_real = MinimalLeastSq(n, real_time, reports[0].complexity_lambda);
|
2016-06-02 20:23:39 +00:00
|
|
|
} else {
|
|
|
|
result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity);
|
|
|
|
result_real = MinimalLeastSq(n, real_time, result_cpu.complexity);
|
2016-06-01 21:08:01 +00:00
|
|
|
}
|
2016-06-02 20:01:31 +00:00
|
|
|
std::string benchmark_name =
|
|
|
|
reports[0].benchmark_name.substr(0, reports[0].benchmark_name.find('/'));
|
2016-05-27 22:45:25 +00:00
|
|
|
|
|
|
|
// Get the data from the accumulator to BenchmarkReporter::Run's.
|
|
|
|
Run big_o;
|
Track 'type' of the run - is it an actual measurement, or an aggregate. (#658)
This is *only* exposed in the JSON. Not in CSV, which is deprecated.
This *only* supposed to track these two states.
An additional field could later track which aggregate this is,
specifically (statistic name, rms, bigo, ...)
The motivation is that we already have ReportAggregatesOnly,
but it affects the entire reports, both the display,
and the reporters (json files), which isn't ideal.
It would be very useful to have a 'display aggregates only' option,
both in the library's console reporter, and the python tooling,
This will be especially needed for the 'store separate iterations'.
2018-08-28 15:11:36 +00:00
|
|
|
big_o.run_type = BenchmarkReporter::Run::RT_Aggregate;
|
2016-05-27 22:45:25 +00:00
|
|
|
big_o.benchmark_name = benchmark_name + "_BigO";
|
|
|
|
big_o.iterations = 0;
|
|
|
|
big_o.real_accumulated_time = result_real.coef;
|
|
|
|
big_o.cpu_accumulated_time = result_cpu.coef;
|
|
|
|
big_o.report_big_o = true;
|
|
|
|
big_o.complexity = result_cpu.complexity;
|
|
|
|
|
2017-04-04 19:31:28 +00:00
|
|
|
// All the time results are reported after being multiplied by the
|
|
|
|
// time unit multiplier. But since RMS is a relative quantity it
|
|
|
|
// should not be multiplied at all. So, here, we _divide_ it by the
|
|
|
|
// multiplier so that when it is multiplied later the result is the
|
|
|
|
// correct one.
|
2016-05-27 22:45:25 +00:00
|
|
|
double multiplier = GetTimeUnitMultiplier(reports[0].time_unit);
|
|
|
|
|
|
|
|
// Only add label to mean/stddev if it is same for all runs
|
|
|
|
Run rms;
|
|
|
|
big_o.report_label = reports[0].report_label;
|
Track 'type' of the run - is it an actual measurement, or an aggregate. (#658)
This is *only* exposed in the JSON. Not in CSV, which is deprecated.
This *only* supposed to track these two states.
An additional field could later track which aggregate this is,
specifically (statistic name, rms, bigo, ...)
The motivation is that we already have ReportAggregatesOnly,
but it affects the entire reports, both the display,
and the reporters (json files), which isn't ideal.
It would be very useful to have a 'display aggregates only' option,
both in the library's console reporter, and the python tooling,
This will be especially needed for the 'store separate iterations'.
2018-08-28 15:11:36 +00:00
|
|
|
rms.run_type = BenchmarkReporter::Run::RT_Aggregate;
|
2016-05-27 22:45:25 +00:00
|
|
|
rms.benchmark_name = benchmark_name + "_RMS";
|
|
|
|
rms.report_label = big_o.report_label;
|
|
|
|
rms.iterations = 0;
|
|
|
|
rms.real_accumulated_time = result_real.rms / multiplier;
|
|
|
|
rms.cpu_accumulated_time = result_cpu.rms / multiplier;
|
|
|
|
rms.report_rms = true;
|
|
|
|
rms.complexity = result_cpu.complexity;
|
2017-04-04 19:31:28 +00:00
|
|
|
// don't forget to keep the time unit, or we won't be able to
|
|
|
|
// recover the correct value.
|
|
|
|
rms.time_unit = reports[0].time_unit;
|
2016-05-27 22:45:25 +00:00
|
|
|
|
|
|
|
results.push_back(big_o);
|
|
|
|
results.push_back(rms);
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2016-05-25 21:13:19 +00:00
|
|
|
} // end namespace benchmark
|