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}