Why trace data is compressed, and what it costs you
The trace unit transmits discontinuities, not instructions. That choice makes trace possible, and it explains every surprise you will have.
A core running at 1 GHz retires on the order of a billion instructions per second. Nobody transmits that. Trace works because it does not try.
The trick
The decoder has the program image. It can therefore answer, for any address, what the next instruction is - unless the flow breaks.
So the trace unit transmits only the breaks:
- conditional branches: which way did it go? One bit, in the best case.
- indirect branches: where did it land? An address, sometimes compressed against the previous one.
- exceptions and interrupts: protocol-specific exception/interrupt information and, where required, address or context information that lets the decoder resume control-flow reconstruction.
- synchronization: periodically, a full anchor so a decoder can join or recover.
Everything else - the long straight stretches, the arithmetic, the loads and stores - is reconstructed by the decoder for free.
Compression shifts information into shared context: the decoder must have the correct program image, architecture state and synchronization history. A small trace stream is therefore not self-contained evidence unless those dependencies are retained.
The consequence you feel
A first-order rate model:
trace bit rate ≈ executed instructions per second
× trace-relevant events per instruction
× average encoded bits per event
+ synchronization, context, timestamp and framing overhead
At a fixed instruction rate, branch and event density is a major driver -
two workloads with identical instruction rates can differ by an order of
magnitude in trace bandwidth. Predictability and protocol features then
determine how cheaply those events encode. A numeric loop with a long body may
be quiet per instruction; a very tight loop may still be branch-dense. An
interpreter or a virtual-call-heavy object-oriented layer is loud. A switch
is expensive only when the compiler lowers it to control flow that requires
target information - depending on compiler and target it may become a jump
table, a decision tree, a lookup or a branchless form. Inspect or measure the
generated code rather than estimating from the source.
This is why “how much bandwidth do I need?” has no general answer, and why anyone who gives you one without asking about your workload is guessing.
What makes the stream longer
Several things inflate trace beyond the minimum, and most of them are switches you control:
Timestamps. Useful for correlating events across cores, expensive in bandwidth. They arrive as their own packets, at whatever frequency you configured.
Cycle counts. Even more expensive. Cycle-accurate information can multiply the packet count for a workload that is otherwise cheap.
Data trace. Reporting memory accesses is a different order of volume from instruction trace. Filtering is not optional here.
Context and privilege changes. Ownership messages that tell the decoder which task or privilege level is running. Modest volume, and usually worth it - without them, a multitasking system’s trace is hard to attribute.
There is a practical rule underneath: if the analysis does not need it, turn it off. Structural coverage needs to know which instructions executed. It does not need cycle counts, and enabling them can push a comfortable configuration into overflow.
What happens when it does not fit
When the path cannot sustain the source, the configured architecture may drop trace and mark a gap, stop or suppress trace, apply backpressure, or stall the processor or hart. Dropping and resynchronizing at the next sync point is the most familiar behavior, not the only one - the policy is a property of the architecture and its configuration.
For debugging, a marked gap is usually survivable. For evidence it is not enough: an affected interval is a window in which you do not know what executed. The evidence record must identify the configured policy, the status mechanism and every affected interval. A coverage result may remain valid outside a bounded, explicitly unresolved interval - but it must not silently bridge the loss, and a report that does not say so is misleading.
Overflow is not something to tune away after the fact. It is avoided at the source - by reducing what is emitted, or by making the exit wider.
Where compression can go further
Beyond the base encodings, further compression mechanisms exist. They do not all have the same standing: some are optional features of standardized protocols or of specific revisions, and some are extensions of a particular encoder implementation. Four that matter in practice:
- Repeated branch patterns. Loops emit the same branch outcome sequence over and over. A mechanism that recognizes the repetition transmits it once.
- Implicit return. If the decoder maintains a call stack, a return does not need its address transmitted at all - it can be inferred. Mechanisms of this kind exist as optional features in standardized protocols, for example an enabled return-stack mechanism in Arm® ETM/ETE; availability is revision-specific.
- Branch prediction agreement. If encoder and decoder run the same predictor, correctly predicted branches need not be transmitted; only the mispredictions.
- Jump target caching. A small cache of recent indirect targets replaces a full address with an index.
Which standing a given mechanism has - standardized feature, optional architecture feature, or an extension of one encoder - is stated in the respective protocol revision or encoder documentation; check it there before relying on the mechanism across toolchains.
Two requirements follow for any implementation of these mechanisms. Each must be lossless within a valid, synchronized stream - the decoder reconstructs exactly the same instruction sequence - and each should be switchable, because a stream that uses such a mechanism is only decodable by a decoder that implements it. Whether a given encoder meets both is implementation evidence to check, not a property a mechanism inherits from appearing in this list.
Our own encoder is one such implementation: each mechanism in it is verified against a reference decoder, the defaults are off, and we publish the encoder as open source; the measured effect of each mechanism is listed on its page.
The practical checklist
Before you conclude that trace does not fit your target:
- Turn off what you do not need. Cycle counts and timestamps first.
- Check the sync frequency. Lower it only after defining the acceptable resynchronization latency and the invalid interval that follows a loss - synchronization you remove is recovery you no longer have.
- Filter. Use only the mechanisms the exact implementation supports - commonly address ranges, and sometimes context, process or task identifiers. Whole-system trace is rarely what you actually want, and filtered evidence is valid only for the declared scope.
- Look at the exit, not the core. Adding a pin or moving to a serial link changes the ceiling by more than any tuning will.
Run this checklist against the actual trace-unit configuration of your part, not against the family datasheet - the implemented options decide the outcome.
Sources
The trace-unit behavior described above (discontinuity encoding, timestamp and cycle-count packets, overflow signaling) is defined in the protocol specifications:
- Embedded Trace Macrocell Architecture Specification, ETMv4.0 to ETMv4.6 (Arm® IHI 0064);
- Intel® 64 and IA-32 Architectures Software Developer’s Manual, volume 3C, chapter on Intel Processor Trace;
- IEEE-ISTO 5001-2012, the Nexus 5001 Forum standard;
- RISC-V® Processor Trace specification (E-Trace).