Introduce additional memory metrics (#1238)

- added total_allocs and net_allocs
  - updated reporter code to report these, if available.
This commit is contained in:
Vy Nguyen 2021-10-18 11:29:35 -04:00 committed by GitHub
parent f730846b0a
commit 7fad964a94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 36 deletions

View File

@ -180,6 +180,7 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
#include <cassert>
#include <cstddef>
#include <iosfwd>
#include <limits>
#include <map>
#include <set>
#include <string>
@ -273,7 +274,6 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
namespace benchmark {
class BenchmarkReporter;
class MemoryManager;
void Initialize(int* argc, char** argv);
void Shutdown();
@ -299,6 +299,49 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter);
size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter,
BenchmarkReporter* file_reporter);
// If a MemoryManager is registered (via RegisterMemoryManager()),
// it can be used to collect and report allocation metrics for a run of the
// benchmark.
class MemoryManager {
public:
static const int64_t TombstoneValue;
struct Result {
Result()
: num_allocs(0),
max_bytes_used(0),
total_allocated_bytes(TombstoneValue),
net_heap_growth(TombstoneValue) {}
// The number of allocations made in total between Start and Stop.
int64_t num_allocs;
// The peak memory use between Start and Stop.
int64_t max_bytes_used;
// The total memory allocated, in bytes, between Start and Stop.
// Init'ed to TombstoneValue if metric not available.
int64_t total_allocated_bytes;
// The net changes in memory, in bytes, between Start and Stop.
// ie., total_allocated_bytes - total_deallocated_bytes.
// Init'ed to TombstoneValue if metric not available.
int64_t net_heap_growth;
};
virtual ~MemoryManager() {}
// Implement this to start recording allocation information.
virtual void Start() = 0;
// Implement this to stop recording and fill out the given Result structure.
BENCHMARK_DEPRECATED_MSG("Use Stop(Result&) instead")
virtual void Stop(Result* result) = 0;
// FIXME(vyng): Make this pure virtual once we've migrated current users.
virtual void Stop(Result& result) { Stop(&result); }
};
// Register a MemoryManager instance that will be used to collect and report
// allocation measurements for benchmark runs.
void RegisterMemoryManager(MemoryManager* memory_manager);
@ -1440,9 +1483,8 @@ class BenchmarkReporter {
report_big_o(false),
report_rms(false),
counters(),
has_memory_result(false),
allocs_per_iter(0.0),
max_bytes_used(0) {}
memory_result(NULL),
allocs_per_iter(0.0) {}
std::string benchmark_name() const;
BenchmarkName run_name;
@ -1493,9 +1535,8 @@ class BenchmarkReporter {
UserCounters counters;
// Memory metrics.
bool has_memory_result;
const MemoryManager::Result* memory_result;
double allocs_per_iter;
int64_t max_bytes_used;
};
struct PerFamilyRunReports {
@ -1624,28 +1665,6 @@ class BENCHMARK_DEPRECATED_MSG(
std::set<std::string> user_counter_names_;
};
// If a MemoryManager is registered, it can be used to collect and report
// allocation metrics for a run of the benchmark.
class MemoryManager {
public:
struct Result {
Result() : num_allocs(0), max_bytes_used(0) {}
// The number of allocations made in total between Start and Stop.
int64_t num_allocs;
// The peak memory use between Start and Stop.
int64_t max_bytes_used;
};
virtual ~MemoryManager() {}
// Implement this to start recording allocation information.
virtual void Start() = 0;
// Implement this to stop recording and fill out the given Result structure.
virtual void Stop(Result* result) = 0;
};
inline const char* GetTimeUnitString(TimeUnit unit) {
switch (unit) {

View File

@ -67,7 +67,7 @@ BenchmarkReporter::Run CreateRunReport(
const benchmark::internal::BenchmarkInstance& b,
const internal::ThreadManager::Result& results,
IterationCount memory_iterations,
const MemoryManager::Result& memory_result, double seconds,
const MemoryManager::Result* memory_result, double seconds,
int64_t repetition_index, int64_t repeats) {
// Create report about this benchmark run.
BenchmarkReporter::Run report;
@ -99,12 +99,12 @@ BenchmarkReporter::Run CreateRunReport(
report.counters = results.counters;
if (memory_iterations > 0) {
report.has_memory_result = true;
assert(memory_result != nullptr);
report.memory_result = memory_result;
report.allocs_per_iter =
memory_iterations ? static_cast<double>(memory_result.num_allocs) /
memory_iterations ? static_cast<double>(memory_result->num_allocs) /
memory_iterations
: 0;
report.max_bytes_used = memory_result.max_bytes_used;
}
internal::Finish(&report.counters, results.iterations, seconds,
@ -302,9 +302,14 @@ void BenchmarkRunner::DoOneRepetition() {
}
// Oh, one last thing, we need to also produce the 'memory measurements'..
MemoryManager::Result memory_result;
MemoryManager::Result* memory_result = nullptr;
IterationCount memory_iterations = 0;
if (memory_manager != nullptr) {
// TODO(vyng): Consider making BenchmarkReporter::Run::memory_result an
// optional so we don't have to own the Result here.
// Can't do it now due to cxx03.
memory_results.push_back(MemoryManager::Result());
memory_result = &memory_results.back();
// Only run a few iterations to reduce the impact of one-time
// allocations in benchmarks that are not properly managed.
memory_iterations = std::min<IterationCount>(16, iters);
@ -316,7 +321,7 @@ void BenchmarkRunner::DoOneRepetition() {
manager->WaitForAllThreads();
manager.reset();
memory_manager->Stop(&memory_result);
memory_manager->Stop(memory_result);
}
// Ok, now actually report.

View File

@ -76,6 +76,8 @@ class BenchmarkRunner {
std::vector<std::thread> pool;
std::vector<MemoryManager::Result> memory_results;
IterationCount iters; // preserved between repetitions!
// So only the first repetition has to find/calculate it,
// the other repetitions will just use that precomputed iteration count.

View File

@ -295,9 +295,20 @@ void JSONReporter::PrintRunData(Run const& run) {
out << ",\n" << indent << FormatKV(c.first, c.second);
}
if (run.has_memory_result) {
if (run.memory_result) {
const MemoryManager::Result memory_result = *run.memory_result;
out << ",\n" << indent << FormatKV("allocs_per_iter", run.allocs_per_iter);
out << ",\n" << indent << FormatKV("max_bytes_used", run.max_bytes_used);
out << ",\n"
<< indent << FormatKV("max_bytes_used", memory_result.max_bytes_used);
auto report_if_present = [&out, &indent](const char* label, int64_t val) {
if (val != MemoryManager::TombstoneValue)
out << ",\n" << indent << FormatKV(label, val);
};
report_if_present("total_allocated_bytes",
memory_result.total_allocated_bytes);
report_if_present("net_heap_growth", memory_result.net_heap_growth);
}
if (!run.report_label.empty()) {
@ -306,4 +317,6 @@ void JSONReporter::PrintRunData(Run const& run) {
out << '\n';
}
const int64_t MemoryManager::TombstoneValue = std::numeric_limits<int64_t>::max();
} // end namespace benchmark