Profiling Pony Programs¶
Pony compiles to native binaries. You profile Pony programs with the same tools you’d use for C or C++ — perf on Linux, Instruments on macOS, Callgrind anywhere Valgrind runs. No special runtime build is needed for perf or Instruments. Callgrind requires a custom ponyc build (see below). No Pony-specific profiler.
Compile with --debug¶
Compile with ponyc --debug before profiling. If your project uses corral, pass the flag through: corral run -- ponyc --debug.
- Disables optimization. Without
--debug, LLVM inlines user functions aggressively. A default build’s profile shows only runtime internals —ponyint_cpu_tick,run_thread,read_msg— because your code was inlined into them. With--debug, your functions appear by name with accurate sample counts. - Adds DWARF debug info. The profiler can map samples to source file and line number, not just function name.
There is no middle ground. ponyc has no separate optimization-level flag — --debug gives you no optimization plus DWARF, and the default gives you full optimization with no DWARF.
The trade-off: --debug profiles show where time goes in unoptimized code. Hot spots in unoptimized code are usually hot in optimized code too, but relative proportions can shift. The optimizer may eliminate overhead that looks significant in the unoptimized profile.
A note on --strip¶
The --strip flag removes DWARF debug sections from a --debug build but leaves the symbol table intact — function names still appear in profiles. Only source-line mapping is lost. On a default (non-debug) build, there are no DWARF sections to remove, so --strip has no effect.
What you’ll see in a profile¶
Pony name mangling¶
Pony compiles each method to a C-level symbol named TypeName_refcap_methodname_types. Some examples from a real profile:
Main_ref__compute_WW— the_computemethod onMain refMain_tag_create_ioo— thecreateconstructor onMain tagcollections_Range_U64_val_ref_has_next_b— thehas_nextmethod onRange[U64]
The type name appears at the start and the method name appears after the reference capability — you can read both at a glance without decoding the rest.
Scheduler threads, not actors¶
The Pony runtime runs N scheduler threads (OS threads). Each thread picks up actors from a queue and runs their behaviors. A profiler samples OS threads, so samples land on scheduler threads — but each sample’s call stack shows which actor behavior was running at that moment. You can still see which of your functions are hot.
There is also an ASIO thread for async I/O. If you see time in ASIO functions, your program is I/O-bound and a CPU profiler won’t show the bottleneck. I/O profiling is outside the scope of this guide.
Runtime functions¶
You’ll see these functions from the Pony runtime in your profiles:
| Function | What it does |
|---|---|
ponyint_actor_run |
The scheduler running an actor’s behavior. Your code appears in the call stack under this function. |
pony_sendv |
Sending a message to an actor. Time here means message-passing overhead. |
pony_create |
Creating a new actor. |
ponyint_gc_mark, ponyint_gc_sweep |
Garbage collection mark and sweep phases. Time here means GC pressure. |
ponyint_sched_* |
Scheduler internals — work stealing, thread coordination. |
How to read the results:
- Most time in your own functions under
ponyint_actor_run— normal. Optimize your code directly. - A lot of time in
ponyint_gc_markorponyint_gc_sweep— GC pressure. See the garbage collector section of the performance cheat sheet. - A lot of time in
pony_sendvorpony_create— too many actors or too many messages. See the design and allocations sections of the cheat sheet.
Linux: perf¶
perf is the standard sampling profiler on Linux.
Record a profile with call graphs, then view the results:
perf record -g ./my-program
perf report
The -g flag records call graphs so you see the full call stack, not just the leaf function that was running when each sample was taken.
perf record needs either root or an appropriate perf_event_paranoid setting. You may need:
sudo sysctl kernel.perf_event_paranoid=1
Flame graphs¶
Flame graphs make hot call paths easy to spot. Two ways to generate them:
With Brendan Gregg’s FlameGraph scripts:
perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg
With perf’s built-in support (recent kernels):
perf script report flamegraph
This produces an HTML flame graph without external scripts.
macOS: Instruments¶
Instruments ships with Xcode. The Time Profiler instrument is the one you want.
You can open Instruments and attach to a running process, or launch the program from Instruments. From the command line:
xctrace record --template 'Time Profiler' --launch ./my-program
Open the resulting .trace file in Instruments for analysis. With --debug, you get source-level detail in the call trees.
Callgrind¶
Callgrind is part of Valgrind. Unlike perf and Instruments, which sample, Callgrind instruments every function call. This makes it precise but slow — programs run 10-50x slower.
Using Callgrind requires a ponyc built with Valgrind annotations so that Valgrind can track allocations through Pony’s memory allocator. This means building ponyc from source. See the Valgrind section of the custom ponyc builds page for instructions.
Record a profile:
valgrind --tool=callgrind ./my-program
View the results as text:
callgrind_annotate callgrind.out.<pid>
Or open the output in KCachegrind (Linux) or QCachegrind (macOS) for a visual call graph.
See also¶
- Tracing Pony Programs — runtime event tracing (actor scheduling, GC, message sends) via a custom
ponycbuild. Different from CPU profiling: tracing records what the runtime did and when, while profiling shows where CPU time went. - DTrace / SystemTap — USDT probes in the Pony runtime for live tracing on Linux (SystemTap) and FreeBSD (DTrace).