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.
Tip
Our examples include a working code-example: statistics/live_events.cpp.
- 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 event_counter = perf::EventCounter{};
try {
/// Events for live monitoring.
event_counter.add_live({"cache-misses", "cache-references", "branches"});
} catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
}Important
We experienced that not mixing live with "traditional" events leads to more consistent results.
Note
Live events can only capture hardware events but not metrics.
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_result(start_values);
/// Computation here...
/// Capture end values after computation.
event_counter.live_result(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:
/// Stop the counter after processing.
event_counter.stop();For further information, refer to the recording basics documentation and the code example.