| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include "benchmark_runner.h" |
|
|
| #include "benchmark/benchmark.h" |
| #include "benchmark_api_internal.h" |
| #include "internal_macros.h" |
|
|
| #ifndef BENCHMARK_OS_WINDOWS |
| #if !defined(BENCHMARK_OS_FUCHSIA) && !defined(BENCHMARK_OS_QURT) |
| #include <sys/resource.h> |
| #endif |
| #include <sys/time.h> |
| #include <unistd.h> |
| #endif |
|
|
| #include <algorithm> |
| #include <atomic> |
| #include <climits> |
| #include <cmath> |
| #include <condition_variable> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <fstream> |
| #include <iostream> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <thread> |
| #include <utility> |
|
|
| #include "check.h" |
| #include "colorprint.h" |
| #include "commandlineflags.h" |
| #include "complexity.h" |
| #include "counter.h" |
| #include "internal_macros.h" |
| #include "log.h" |
| #include "mutex.h" |
| #include "perf_counters.h" |
| #include "re.h" |
| #include "statistics.h" |
| #include "string_util.h" |
| #include "thread_manager.h" |
| #include "thread_timer.h" |
|
|
| namespace benchmark { |
|
|
| namespace internal { |
|
|
| MemoryManager* memory_manager = nullptr; |
|
|
| namespace { |
|
|
| static constexpr IterationCount kMaxIterations = 1000000000; |
| const double kDefaultMinTime = |
| std::strtod(::benchmark::kDefaultMinTimeStr, nullptr); |
|
|
| BenchmarkReporter::Run CreateRunReport( |
| const benchmark::internal::BenchmarkInstance& b, |
| const internal::ThreadManager::Result& results, |
| IterationCount memory_iterations, |
| const MemoryManager::Result* memory_result, double seconds, |
| int64_t repetition_index, int64_t repeats) { |
| |
| BenchmarkReporter::Run report; |
|
|
| report.run_name = b.name(); |
| report.family_index = b.family_index(); |
| report.per_family_instance_index = b.per_family_instance_index(); |
| report.skipped = results.skipped_; |
| report.skip_message = results.skip_message_; |
| report.report_label = results.report_label_; |
| |
| report.iterations = results.iterations; |
| report.time_unit = b.time_unit(); |
| report.threads = b.threads(); |
| report.repetition_index = repetition_index; |
| report.repetitions = repeats; |
|
|
| if (!report.skipped) { |
| if (b.use_manual_time()) { |
| report.real_accumulated_time = results.manual_time_used; |
| } else { |
| report.real_accumulated_time = results.real_time_used; |
| } |
| report.cpu_accumulated_time = results.cpu_time_used; |
| report.complexity_n = results.complexity_n; |
| report.complexity = b.complexity(); |
| report.complexity_lambda = b.complexity_lambda(); |
| report.statistics = &b.statistics(); |
| report.counters = results.counters; |
|
|
| if (memory_iterations > 0) { |
| assert(memory_result != nullptr); |
| report.memory_result = memory_result; |
| report.allocs_per_iter = |
| memory_iterations ? static_cast<double>(memory_result->num_allocs) / |
| memory_iterations |
| : 0; |
| } |
|
|
| internal::Finish(&report.counters, results.iterations, seconds, |
| b.threads()); |
| } |
| return report; |
| } |
|
|
| |
| |
| void RunInThread(const BenchmarkInstance* b, IterationCount iters, |
| int thread_id, ThreadManager* manager, |
| PerfCountersMeasurement* perf_counters_measurement) { |
| internal::ThreadTimer timer( |
| b->measure_process_cpu_time() |
| ? internal::ThreadTimer::CreateProcessCpuTime() |
| : internal::ThreadTimer::Create()); |
|
|
| State st = |
| b->Run(iters, thread_id, &timer, manager, perf_counters_measurement); |
| BM_CHECK(st.skipped() || st.iterations() >= st.max_iterations) |
| << "Benchmark returned before State::KeepRunning() returned false!"; |
| { |
| MutexLock l(manager->GetBenchmarkMutex()); |
| internal::ThreadManager::Result& results = manager->results; |
| results.iterations += st.iterations(); |
| results.cpu_time_used += timer.cpu_time_used(); |
| results.real_time_used += timer.real_time_used(); |
| results.manual_time_used += timer.manual_time_used(); |
| results.complexity_n += st.complexity_length_n(); |
| internal::Increment(&results.counters, st.counters); |
| } |
| manager->NotifyThreadComplete(); |
| } |
|
|
| double ComputeMinTime(const benchmark::internal::BenchmarkInstance& b, |
| const BenchTimeType& iters_or_time) { |
| if (!IsZero(b.min_time())) return b.min_time(); |
| |
| |
| if (iters_or_time.tag == BenchTimeType::ITERS) return kDefaultMinTime; |
|
|
| return iters_or_time.time; |
| } |
|
|
| IterationCount ComputeIters(const benchmark::internal::BenchmarkInstance& b, |
| const BenchTimeType& iters_or_time) { |
| if (b.iterations() != 0) return b.iterations(); |
|
|
| |
| |
| BM_CHECK(iters_or_time.tag == BenchTimeType::ITERS); |
| return iters_or_time.iters; |
| } |
|
|
| } |
|
|
| BenchTimeType ParseBenchMinTime(const std::string& value) { |
| BenchTimeType ret; |
|
|
| if (value.empty()) { |
| ret.tag = BenchTimeType::TIME; |
| ret.time = 0.0; |
| return ret; |
| } |
|
|
| if (value.back() == 'x') { |
| char* p_end; |
| |
| errno = 0; |
| IterationCount num_iters = std::strtol(value.c_str(), &p_end, 10); |
|
|
| |
| |
| BM_CHECK(errno == 0 && p_end != nullptr && *p_end == 'x') |
| << "Malformed iters value passed to --benchmark_min_time: `" << value |
| << "`. Expected --benchmark_min_time=<integer>x."; |
|
|
| ret.tag = BenchTimeType::ITERS; |
| ret.iters = num_iters; |
| return ret; |
| } |
|
|
| bool has_suffix = value.back() == 's'; |
| if (!has_suffix) { |
| BM_VLOG(0) << "Value passed to --benchmark_min_time should have a suffix. " |
| "Eg., `30s` for 30-seconds."; |
| } |
|
|
| char* p_end; |
| |
| errno = 0; |
| double min_time = std::strtod(value.c_str(), &p_end); |
|
|
| |
| |
| BM_CHECK(errno == 0 && p_end != nullptr && |
| ((has_suffix && *p_end == 's') || *p_end == '\0')) |
| << "Malformed seconds value passed to --benchmark_min_time: `" << value |
| << "`. Expected --benchmark_min_time=<float>x."; |
|
|
| ret.tag = BenchTimeType::TIME; |
| ret.time = min_time; |
|
|
| return ret; |
| } |
|
|
| BenchmarkRunner::BenchmarkRunner( |
| const benchmark::internal::BenchmarkInstance& b_, |
| PerfCountersMeasurement* pcm_, |
| BenchmarkReporter::PerFamilyRunReports* reports_for_family_) |
| : b(b_), |
| reports_for_family(reports_for_family_), |
| parsed_benchtime_flag(ParseBenchMinTime(FLAGS_benchmark_min_time)), |
| min_time(ComputeMinTime(b_, parsed_benchtime_flag)), |
| min_warmup_time((!IsZero(b.min_time()) && b.min_warmup_time() > 0.0) |
| ? b.min_warmup_time() |
| : FLAGS_benchmark_min_warmup_time), |
| warmup_done(!(min_warmup_time > 0.0)), |
| repeats(b.repetitions() != 0 ? b.repetitions() |
| : FLAGS_benchmark_repetitions), |
| has_explicit_iteration_count(b.iterations() != 0 || |
| parsed_benchtime_flag.tag == |
| BenchTimeType::ITERS), |
| pool(b.threads() - 1), |
| iters(has_explicit_iteration_count |
| ? ComputeIters(b_, parsed_benchtime_flag) |
| : 1), |
| perf_counters_measurement_ptr(pcm_) { |
| run_results.display_report_aggregates_only = |
| (FLAGS_benchmark_report_aggregates_only || |
| FLAGS_benchmark_display_aggregates_only); |
| run_results.file_report_aggregates_only = |
| FLAGS_benchmark_report_aggregates_only; |
| if (b.aggregation_report_mode() != internal::ARM_Unspecified) { |
| run_results.display_report_aggregates_only = |
| (b.aggregation_report_mode() & |
| internal::ARM_DisplayReportAggregatesOnly); |
| run_results.file_report_aggregates_only = |
| (b.aggregation_report_mode() & internal::ARM_FileReportAggregatesOnly); |
| BM_CHECK(FLAGS_benchmark_perf_counters.empty() || |
| (perf_counters_measurement_ptr->num_counters() == 0)) |
| << "Perf counters were requested but could not be set up."; |
| } |
| } |
|
|
| BenchmarkRunner::IterationResults BenchmarkRunner::DoNIterations() { |
| BM_VLOG(2) << "Running " << b.name().str() << " for " << iters << "\n"; |
|
|
| std::unique_ptr<internal::ThreadManager> manager; |
| manager.reset(new internal::ThreadManager(b.threads())); |
|
|
| |
| for (std::size_t ti = 0; ti < pool.size(); ++ti) { |
| pool[ti] = std::thread(&RunInThread, &b, iters, static_cast<int>(ti + 1), |
| manager.get(), perf_counters_measurement_ptr); |
| } |
| |
| |
| |
| RunInThread(&b, iters, 0, manager.get(), perf_counters_measurement_ptr); |
|
|
| |
| manager->WaitForAllThreads(); |
| for (std::thread& thread : pool) thread.join(); |
|
|
| IterationResults i; |
| |
| { |
| MutexLock l(manager->GetBenchmarkMutex()); |
| i.results = manager->results; |
| } |
|
|
| |
| manager.reset(); |
|
|
| |
| i.results.real_time_used /= b.threads(); |
| i.results.manual_time_used /= b.threads(); |
| |
| if (b.measure_process_cpu_time()) i.results.cpu_time_used /= b.threads(); |
|
|
| BM_VLOG(2) << "Ran in " << i.results.cpu_time_used << "/" |
| << i.results.real_time_used << "\n"; |
|
|
| |
| |
| i.iters = i.results.iterations / b.threads(); |
|
|
| |
| i.seconds = i.results.cpu_time_used; |
| if (b.use_manual_time()) { |
| i.seconds = i.results.manual_time_used; |
| } else if (b.use_real_time()) { |
| i.seconds = i.results.real_time_used; |
| } |
|
|
| return i; |
| } |
|
|
| IterationCount BenchmarkRunner::PredictNumItersNeeded( |
| const IterationResults& i) const { |
| |
| |
| double multiplier = GetMinTimeToApply() * 1.4 / std::max(i.seconds, 1e-9); |
| |
| |
| |
| |
| |
| const bool is_significant = (i.seconds / GetMinTimeToApply()) > 0.1; |
| multiplier = is_significant ? multiplier : 10.0; |
|
|
| |
| const IterationCount max_next_iters = static_cast<IterationCount>( |
| std::lround(std::max(multiplier * static_cast<double>(i.iters), |
| static_cast<double>(i.iters) + 1.0))); |
| |
| const IterationCount next_iters = std::min(max_next_iters, kMaxIterations); |
|
|
| BM_VLOG(3) << "Next iters: " << next_iters << ", " << multiplier << "\n"; |
| return next_iters; |
| } |
|
|
| bool BenchmarkRunner::ShouldReportIterationResults( |
| const IterationResults& i) const { |
| |
| |
| |
| return i.results.skipped_ || |
| i.iters >= kMaxIterations || |
| i.seconds >= |
| GetMinTimeToApply() || |
| |
| |
| |
| ((i.results.real_time_used >= 5 * GetMinTimeToApply()) && |
| !b.use_manual_time()); |
| } |
|
|
| double BenchmarkRunner::GetMinTimeToApply() const { |
| |
| |
| |
| |
| |
| return warmup_done ? min_time : min_warmup_time; |
| } |
|
|
| void BenchmarkRunner::FinishWarmUp(const IterationCount& i) { |
| warmup_done = true; |
| iters = i; |
| } |
|
|
| void BenchmarkRunner::RunWarmUp() { |
| |
| |
| IterationResults i_warmup; |
| |
| |
| |
| |
| |
| |
| const IterationCount i_backup = iters; |
|
|
| for (;;) { |
| b.Setup(); |
| i_warmup = DoNIterations(); |
| b.Teardown(); |
|
|
| const bool finish = ShouldReportIterationResults(i_warmup); |
|
|
| if (finish) { |
| FinishWarmUp(i_backup); |
| break; |
| } |
|
|
| |
| |
| |
| |
| |
| iters = PredictNumItersNeeded(i_warmup); |
| assert(iters > i_warmup.iters && |
| "if we did more iterations than we want to do the next time, " |
| "then we should have accepted the current iteration run."); |
| } |
| } |
|
|
| void BenchmarkRunner::DoOneRepetition() { |
| assert(HasRepeatsRemaining() && "Already done all repetitions?"); |
|
|
| const bool is_the_first_repetition = num_repetitions_done == 0; |
|
|
| |
| |
| |
| |
| |
| if (!warmup_done) RunWarmUp(); |
|
|
| IterationResults i; |
| |
| |
| |
| |
| |
| |
| for (;;) { |
| b.Setup(); |
| i = DoNIterations(); |
| b.Teardown(); |
|
|
| |
| |
| |
| |
| |
| const bool results_are_significant = !is_the_first_repetition || |
| has_explicit_iteration_count || |
| ShouldReportIterationResults(i); |
|
|
| if (results_are_significant) break; |
|
|
| |
| |
|
|
| iters = PredictNumItersNeeded(i); |
| assert(iters > i.iters && |
| "if we did more iterations than we want to do the next time, " |
| "then we should have accepted the current iteration run."); |
| } |
|
|
| |
| MemoryManager::Result* memory_result = nullptr; |
| IterationCount memory_iterations = 0; |
| if (memory_manager != nullptr) { |
| |
| |
| |
| memory_results.push_back(MemoryManager::Result()); |
| memory_result = &memory_results.back(); |
| |
| |
| memory_iterations = std::min<IterationCount>(16, iters); |
| memory_manager->Start(); |
| std::unique_ptr<internal::ThreadManager> manager; |
| manager.reset(new internal::ThreadManager(1)); |
| b.Setup(); |
| RunInThread(&b, memory_iterations, 0, manager.get(), |
| perf_counters_measurement_ptr); |
| manager->WaitForAllThreads(); |
| manager.reset(); |
| b.Teardown(); |
| memory_manager->Stop(*memory_result); |
| } |
|
|
| |
| BenchmarkReporter::Run report = |
| CreateRunReport(b, i.results, memory_iterations, memory_result, i.seconds, |
| num_repetitions_done, repeats); |
|
|
| if (reports_for_family) { |
| ++reports_for_family->num_runs_done; |
| if (!report.skipped) reports_for_family->Runs.push_back(report); |
| } |
|
|
| run_results.non_aggregates.push_back(report); |
|
|
| ++num_repetitions_done; |
| } |
|
|
| RunResults&& BenchmarkRunner::GetResults() { |
| assert(!HasRepeatsRemaining() && "Did not run all repetitions yet?"); |
|
|
| |
| run_results.aggregates_only = ComputeStats(run_results.non_aggregates); |
|
|
| return std::move(run_results); |
| } |
|
|
| } |
|
|
| } |
|
|