The perf-cpp library supports reading hardware performance counter values without stopping the counters ("live" events), particularly on x86
systems using the rdpmc instruction.
This feature allows for interim results during ongoing computations, ideal for real-time monitoring and adjustments.
The perf::EventCounter
class is designed to support both standard and "live" events, allowing configuration of hardware performance counters to access results either "live" (for interim results) or after stopping.
For the latter, see the recording basics documentation.
→ View a working code-example here.
- Setting Up Live Events
- Initializing the Hardware Counters (optional)
- Reading Live Events During Computation
- Finalizing and Retrieving Results
Define which events to monitor live and which to read post-computation using the perf::EventCounter
:
#include <perfcpp/event_counter.h>
auto counters = perf::CounterDefinition{};
auto event_counter = perf::EventCounter{counters};
try {
/// Events for live monitoring.
event_counter.add_live({"cache-misses", "cache-references"});
/// Traditional events for post-processing analysis.
event_counter.add({"instructions", "cycles", "branches", "branch-misses", "cache-misses", "cache-references"});
} catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
Optionally, preparing the hardware counters ahead of time to exclude configuration time from your measurements, though this is also handled automatically at the start if skipped:
try {
event_counter.open();
} catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
The library provides two methods for accessing live events during computation: directly via the EventCounter
and using a simplified LiveEventCounter
wrapper.
Events added as live events (via add_live()
) can be directly accessed from the EventCounter
without stopping.
To be efficient, read live event counts by pre-allocating memory for the results to avoid allocation overheads during critical measurement phases:
try {
event_counter.start();
} catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
/// Pre-allocated containers for live results.
auto start_values = std::vector<double>{/* cache-misses */ .0, /* cache-references */ .0};
auto end_values = std::vector<double>{/* cache-misses */ .0, /* cache-references */ .0};
for (auto i = 0U; i < runs; ++i) {
/// Capture start values.
event_counter.live_results(start_values);
/// Computation here...
/// Capture end values after computation.
event_counter.live_results(end_values);
std::cout << "Live Results: "
<< "cache-misses: " << end_values[0U] - start_values[0U] << ","
<< "cache-references: " << end_values[1U] - start_values[1U] << std::endl;
}
The LiveEventCounter
provides a streamlined method to manage live event monitoring by handling memory management and calculation of differences internally.
/// Initiate the LiveEventCounter wrapper before starting.
auto live_event_counter = perf::LiveEventCounter{ event_counter };
try {
event_counter.start();
} catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}
for (auto i = 0U; i < runs; ++i) {
/// Capture start values.
live_event_counter.start();
/// Computation here...
/// Capture end values after computation.
live_event_counter.stop();
std::cout << "Live Results: "
<< "cache-misses: " << live_event_counter.get("cache-misses") << ","
<< "cache-references: " << live_event_counter.get("cache-references") << std::endl;
}
Upon completion, stop the counters and fetch final results for non-live events:
/// Stop the counter after processing.
event_counter.stop();
/// Calculate the result.
const auto result = event_counter.result();
//// Or print the results as table.
std::cout << result.to_string() << std::endl;
For further information, refer to the recording basics documentation.