Inspect ParallelFor profiling from C++#
This runnable example creates a fixed-capacity collector, passes it through
onnx_light::core::runtime::RuntimeSessionOptions, runs inference,
and requests an owning onnx_light::core::runtime::ParallelRegionReport.
The report copies its events and dropped count, so it can be retained without
exposing or depending on the collector’s live storage.
Build an installed onnx-light tree and run the example with:
cmake -S examples/parallel_for_profiling -B build-parallel-for-profiling \
-DCMAKE_PREFIX_PATH=/usr/local
cmake --build build-parallel-for-profiling
./build-parallel-for-profiling/parallel_for_profiling
Portable timing is the default and performs no hardware-counter syscall. On
Linux, pass --hardware-counters to request one perf_event_open group
containing cycles, retired instructions, LLC references, and LLC misses:
./build-parallel-for-profiling/parallel_for_profiling --hardware-counters
Access may require CAP_PERFMON or a sufficiently permissive
kernel.perf_event_paranoid value (commonly 1 or lower). The report
distinguishes unsupported, permission_denied, multiplexed,
overflowed, and valid samples. This backend deliberately rejects
multiplexed samples instead of scaling them: IPC and LLC miss rate are present
only for isolated single-thread regions when time_running == time_enabled.
Multi-thread regions are also marked multiplexed because one perf group
cannot represent their aggregate work. When counters are unavailable, the
example still reports portable wall and process CPU timing. Non-Linux systems
report an opted-in request as unsupported.
For an isolated, single-threaded workload, compare a valid sample with
perf stat -e cycles,instructions,cache-references,cache-misses. Counts from
separate runs are expected to agree within 10%; pin the workload to one CPU and
repeat it if frequency scaling or scheduler activity causes more variance.
The collector capacity is one and the session runs twice, so the report contains one event and reports one dropped event.
1// Copyright (c) ONNX Project Contributors
2//
3// SPDX-License-Identifier: Apache-2.0
4
5#include "onnx_core/runtime/runtime_context.h"
6#include "onnx_core/runtime/runtime_session.h"
7#include "onnx_extensions/kernels/kernel_dispatch_table.h"
8#include "onnx_proto/onnx.h"
9
10#include <cstdint>
11#include <iostream>
12#include <memory>
13#include <string_view>
14#include <vector>
15
16namespace rt = ONNX_LIGHT_NAMESPACE::core::runtime;
17using ONNX_LIGHT_NAMESPACE::GraphProto;
18using ONNX_LIGHT_NAMESPACE::NodeProto;
19using ONNX_LIGHT_NAMESPACE::ValueInfoProto;
20
21GraphProto MakeAbsGraph() {
22 GraphProto graph;
23 graph.set_name("profile_abs");
24 ValueInfoProto input;
25 input.set_name("x");
26 ValueInfoProto output;
27 output.set_name("y");
28 graph.ref_input().push_back(input);
29 graph.ref_output().push_back(output);
30 NodeProto node;
31 node.set_op_type("Abs");
32 node.add_input("x");
33 node.add_output("y");
34 graph.ref_node().push_back(node);
35 return graph;
36}
37
38int main(int argc, char **argv) {
39 const bool hardware_counters = argc == 2 && std::string_view(argv[1]) == "--hardware-counters";
40 if (argc > 2 || (argc == 2 && !hardware_counters)) {
41 std::cerr << "Usage: " << argv[0] << " [--hardware-counters]\n";
42 return 1;
43 }
44 ONNX_LIGHT_NAMESPACE::onnx_kernels::RegisterKernelFunctions();
45 const GraphProto graph = MakeAbsGraph();
46 rt::RuntimeContext context(rt::KernelContext(rt::DefaultOpset(18)));
47 context.Set("x", rt::Tensor::FromFloat("x", {100000}, std::vector<float>(100000, -1.0f)));
48
49 auto collector = std::make_shared<rt::ParallelRegionCollector>(1, hardware_counters);
50 rt::RuntimeSession session(context.GetExecutionPlan(graph),
51 rt::RuntimeSessionOptions{
52 .parameters = rt::RuntimeParameters(hardware_counters ? 1 : 2),
53 .parallel_region_collector = collector,
54 });
55 session.Run(context);
56 session.Run(context);
57
58 const rt::ParallelRegionReport report = session.parallel_region_report();
59 for (const rt::ParallelRegionReportEvent &event : report.events()) {
60 std::cout << event.file_name << ":" << event.line << " requested=" << event.requested_threads
61 << " admitted=" << event.admitted_threads << " observed=" << event.observed_threads
62 << " wall_time_ns=" << event.wall_time_ns.value_or(0)
63 << " counters=" << rt::HardwareCounterStatusName(event.counter_status);
64 if (event.ipc.has_value()) {
65 std::cout << " ipc=" << *event.ipc;
66 }
67 if (event.llc_miss_rate.has_value()) {
68 std::cout << " llc_miss_rate=" << *event.llc_miss_rate;
69 }
70 if (!event.ipc.has_value() && !event.llc_miss_rate.has_value()) {
71 std::cout << " (using portable timing)";
72 } else {
73 std::cout << " (hardware metrics available)";
74 }
75 std::cout << "\n";
76 }
77 std::cout << "dropped_events=" << report.dropped_events() << "\n";
78 return 0;
79}