Docs / Concepts / Profiling
Profilingv0.2.0
You cannot optimize an inference you cannot measure. syn_prof gives every inference a per-stage timing breakdown, a peak-memory figure, and an NPU utilization percentage — captured with the hardware cycle counter, printable from the shell, and free when you turn it off.
What gets measured
One inference produces one syn_prof_result_t:
typedef struct {
uint32_t total_us; /* Total inference time */
uint32_t preprocess_us; /* Pre-processing time */
uint32_t npu_us; /* NPU execution time */
uint32_t postprocess_us; /* Post-processing time */
uint32_t ipc_overhead_us; /* IPC latency (if cross-core) */
uint32_t mem_peak_bytes; /* Peak SRAM usage during inference */
uint32_t npu_utilization_pct; /* NPU busy time as % of total */
} syn_prof_result_t;Timing is cycle-accurate: the profiler marks stage boundaries with k_cycle_get_32() and converts the deltas with k_cyc_to_us_ceil32(). Peak memory is read from the tensor arena's own statistics (syn_mem_get_stats(), the arena_peak field) at the end of the run, and NPU utilization is computed as NPU time as a percentage of total time. The ipc_overhead_us field is reserved for cross-core inference (see Dual-core design & IPC).
The C API
int syn_prof_enable(void); int syn_prof_disable(void); int syn_prof_get_last(syn_prof_result_t *result); void syn_prof_print_summary(void); /* Per-layer tracing */ int syn_prof_enable_layer_trace(void); int syn_prof_get_layer_time(uint32_t layer_index, uint32_t *us);
syn_prof_get_last() returns the most recent result, or -ENOENT when no inference has been profiled yet. syn_prof_print_summary() dumps the same data to the log. Internally, the inference path calls a set of markers — syn_prof_mark_start(), syn_prof_mark_preprocess_done(), syn_prof_mark_npu_done(), syn_prof_mark_end() — that record the cycle counter at each stage boundary.
The layer-trace entry points exist in the frozen API, but both syn_prof_enable_layer_trace() and syn_prof_get_layer_time() still return -ENOTSUP as of Phase 2. Layer-granularity work is scheduled alongside the later layer-boundary preemption features, which need real per-layer NPU callbacks.
Zero overhead when disabled
Profiling costs nothing when you are not using it, at two levels. At build time, CONFIG_SYNAPTIC_PROFILING (default y if DEBUG) gates the feature entirely — zero overhead when disabled. At runtime, every marker checks the enabled flag first and returns immediately when profiling is off, so syn_prof_disable() reduces a marker to a single branch.
Shell commands
The syn prof command group exposes the profiler over the syn shell:
| Command | Effect |
|---|---|
syn prof last | Print the last profiling result (total, preprocess, NPU, postprocess times) |
syn prof enable | Enable profiling |
syn prof disable | Disable profiling |
After a syn infer run test_classify on the FRDM-MCXN947 (v0.2.0 verification, 2026-07-12; stub NPU backend):
uart:~$ syn prof last Last inference: Total: 1069 us Preprocess: 1 us NPU: 1068 us Postprocess: 1 us Memory peak: 1792 bytes
The corresponding NPU utilization is 99% — expected for a bare single-model pipeline where the (stub-simulated) accelerator dominates and the pre/post stages are trivial. On QEMU (icount timing) the hello_inference boot inference profiles at 1010 µs total / 1003 µs NPU.
Status in Phase 2
In v0.1.0 the profiler core, the syn_prof_mark_* internal API, and the shell commands were implemented, but no inference path called the markers — syn prof last always answered No profiling data available. As of v0.2.0 the pipeline executor fires the marks at every stage boundary and hello_inference runs through syn_infer_run_sync(), so syn prof last returns real data — verified live on the FRDM-MCXN947 on 2026-07-12 (the capture above), closing the Phase 1 known limitation on hardware. NPU times are the deterministic stub backend. The Phase 1 hardware figures (1038 µs end-to-end on the FRDM board versus 781 µs on QEMU, direct-HAL path) stand unchanged.