> ## Documentation Index
> Fetch the complete documentation index at: https://docs.embedder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Performance optimization

> Measure firmware speed, memory footprint, and power on the intended target, make bounded changes with Embedder, and re-run the same workload.

Use this workflow to improve a firmware budget with evidence from the intended build and target. Choose one metric, record a reproducible baseline, make a bounded change, and repeat the same measurement before drawing a conclusion.

<Note>
  Embedder provides build, telemetry, debugger, trace, coverage, and power-measurement workflows. It does not maintain an automatic performance-baseline database or calculate a before-and-after report for every change. Record the protocol and results in the project so another run can reproduce the comparison.
</Note>

## Choose one primary metric

State the workload, unit, acceptance threshold, and measurement method before changing code.

| Budget                        | Primary evidence                                      | Supporting evidence                     |
| ----------------------------- | ----------------------------------------------------- | --------------------------------------- |
| Flash and RAM                 | Linker map, toolchain size output, and final artifact | Section and symbol inspection           |
| Function or interrupt latency | GPIO marker with logic analyzer or oscilloscope       | Target timer or cycle counter           |
| Throughput                    | Count over a fixed interval under a defined workload  | Serial telemetry and protocol capture   |
| CPU or task activity          | Structured RTT or ITM events                          | GDB state and application counters      |
| Executed path                 | ETM instruction trace                                 | Symbols from the matching ELF           |
| Function or source coverage   | J-Trace coverage report                               | Test and workload description           |
| Average or peak current       | PPK2 or Joulescope capture                            | Firmware state markers                  |
| Energy per operation          | Joulescope current and voltage over a bounded window  | Operation count and UART or GPIO marker |

Do not optimize "performance" as one combined number. A smaller image can run more slowly; lower average current can hide a longer active period; extra telemetry can change timing and power.

## Write a repeatable measurement protocol

<Steps>
  <Step title="Define the workload">
    Specify inputs, duration or operation count, firmware state, peripheral activity, and environmental conditions.

    ```text theme={"system"}
    Measure wake-to-first-sample latency for 100 wake cycles with the sensor at
    100 Hz, UART logging disabled, and the release build at 3.3 V.
    ```
  </Step>

  <Step title="Fix the build configuration">
    Record the source revision, compiler and version, optimization flags, linker script, feature flags, and target artifact. Compare like with like.
  </Step>

  <Step title="Choose the observation path">
    Prefer a measurement that minimally perturbs the behavior. Use a pin or instrument for timing-sensitive code, and use instrumented logs only when their overhead is acceptable.
  </Step>

  <Step title="Record setup and provenance">
    Save the board revision, probe or instrument, wiring, supply voltage, warm-up period, requested and achieved sample rate, trigger, and matching ELF.
  </Step>

  <Step title="Run the baseline more than once">
    Use enough repetitions to expose variation. Report the distribution or at least minimum, maximum, and average when one sample is not representative.
  </Step>
</Steps>

Keep the protocol in `EMBEDDER.md`, a benchmark file, or a hardware case under `.embedder/hardware/cases/`. A prose statement such as "power was lower" is not a reproducible baseline.

## Measure flash and RAM

Embedder uses the project's shell commands and toolchain output for image size; there is no separate firmware-size tool.

Ask it to build the exact configuration, preserve the linker map, and report sections rather than only the artifact's file size:

```text theme={"system"}
Build the release image using the command in EMBEDDER.md. Run the project's
size report and inspect the linker map. Report flash, initialized RAM, zeroed
RAM, and the ten largest first-party symbols. Keep debug and bootloader regions
separate. Do not compare against a different configuration.
```

For a candidate change:

```text theme={"system"}
Rebuild with the same toolchain, flags, linker script, and feature set. Compare
the same sections and symbols with the recorded baseline. Report absolute and
percentage deltas, plus any section that moved memory regions.
```

Check the map when a total changes unexpectedly. Generated tables, unwind data, alignment, link-time optimization, and dead-code removal can move bytes without an obvious source-line change.

## Measure latency and throughput

Choose the observation method by the timescale and required confidence.

### Use target telemetry for trends

Emit numeric Teleplot lines over serial or RTT when the firmware can tolerate logging:

```c theme={"system"}
Serial.printf(">loop_us:%lu§us\r\n", loop_duration_us);
Serial.printf(">samples_per_s:%lu§Hz\r\n", sample_rate);
```

In the VS Code extension, ask Embedder to start a plot, confirm the discovered channels, and save the recording:

```text theme={"system"}
Plot loop_us and samples_per_s from the selected RTT transport for 60 seconds.
Save the recording and report the steady-state range after the first 10 seconds.
```

Device timestamps preserve spacing when transport output is batched. Logging from a tight interrupt or high-rate loop can perturb the workload, so compare against an uninstrumented physical measurement when the overhead matters.

### Use a logic analyzer or oscilloscope for real timing

Toggle a GPIO at the boundaries of the operation, then capture it with the bus or electrical signal:

```text theme={"system"}
Capture the request marker and SPI clock for 100 transactions. Measure marker
to first clock, transaction duration, and idle gap using the achieved sample
rate. Save the raw capture and decoded SPI rows.
```

Use an oscilloscope when analog thresholds, rise time, rail behavior, or pulse shape affect the result. Use a logic analyzer when decoded protocol traffic and digital edge timing answer the question.

### Use GDB for state, not transparent timing

GDB can inspect variables, registers, memory, stacks, and task views. Halting, stepping, and breakpoints perturb real-time behavior. Use it to explain a measured bottleneck or confirm configuration, not as the only proof of latency.

```text theme={"system"}
Attach without programming, halt once after the slow-path marker, and read the
active frame, DMA state, and queue depth. Continue immediately after the read.
Report that the halt disturbed timing.
```

## Measure execution and coverage

Choose the trace type by the question:

* RTT or ITM events show firmware-generated application or RTOS events over time.
* `instruction_trace` records the most recent bounded ETM instruction window.
* `coverage_run` reports function, source-line, and instruction coverage for a bounded run.
* Streamed ETM starts from a known point and retains a longer rolling instruction history.

ETM capture requires a SEGGER J-Trace, fine-pitch CoreSight-20 cable, target ETM routing, SEGGER host software, a valid device name, and the matching ELF.

```text theme={"system"}
Run five seconds of source coverage for build/release/app.elf under the recorded
benchmark workload. Report function and line coverage plus function entry
counts. Save the report ID and ELF match state.
```

```text theme={"system"}
Capture a bounded ETM instruction window around the slow request. Show the
newest 1000 symbolized instructions and reconstructed frames. Do not infer
elapsed time from instruction order.
```

Coverage can show that a workload reached a function; it does not prove that the function is a bottleneck. ETM instruction order has no real timestamps. A CPU frequency can produce an estimate, not a measured duration.

## Measure current and energy

Use a Nordic PPK2 or Jetperch Joulescope in Debug mode. Run `hardware_status` first, identify the exact instrument, and confirm wiring before a measurement.

### PPK2

* Use source-meter mode when the PPK2 supplies the device.
* Use ampere-meter mode when an external supply powers the device.
* On a Nordic DK, remove the current-measurement jumper for inline measurement and restore it before using the on-board debugger interfaces.

```text theme={"system"}
The PPK2 switch is in source-meter mode and no other supply is connected.
Supply 3300 mV, wait 100 ms, run the idle workload for five seconds, and
return average, minimum, and maximum current.
```

### Joulescope

A Joulescope is an inline analyzer and does not supply the target. Use an external supply. The JS220 can decode UART while measuring current and voltage, which provides one instrument timebase for state markers and power:

```text theme={"system"}
The JS220 is wired inline to the external 3.3 V supply, and DUT TX is connected
to gpi1. Decode UART at 115200 baud while measuring for five seconds. Return
the enter_sleep and wake markers with average current, charge, and energy.
```

One-shot Joulescope measurements buffer up to 60 seconds in memory. Use streaming statistics or a JLS recording for a longer run.

## Make one bounded change

Use Plan mode when an optimization changes architecture, scheduling, memory layout, clocking, or power-state policy. Require the plan to identify the expected mechanism, tradeoffs, and verification protocol.

Use Act mode for a focused implementation:

```text theme={"system"}
Replace the linear lookup in @src/router.c with the existing indexed table.
Keep ordering and the public API unchanged. Run the router tests, rebuild the
same release configuration, then rerun the recorded throughput benchmark.
```

Avoid combining unrelated optimizations in one measurement. If the metric moves, a bounded diff makes the cause reviewable; if it regresses, the rollback is clear.

## Re-measure under the same conditions

<Steps>
  <Step title="Rebuild from the candidate revision">
    Use the same toolchain, configuration, linker script, and feature flags.
  </Step>

  <Step title="Restore the target state">
    Use the same board, power source, peripheral state, warm-up, and starting condition. Record any reset or reflash.
  </Step>

  <Step title="Run the unchanged protocol">
    Keep the workload, duration, trigger, sample rate, telemetry, and analysis method identical.
  </Step>

  <Step title="Report the comparison">
    Include baseline, candidate, absolute delta, percentage delta where meaningful, run-to-run variation, and measurement uncertainty.
  </Step>

  <Step title="Check secondary budgets">
    Confirm that a speed improvement did not violate flash, RAM, power, correctness, timing, or maintainability constraints.
  </Step>
</Steps>

Use a result record like:

| Field               | Baseline                  | Candidate                 |
| ------------------- | ------------------------- | ------------------------- |
| Source revision     | `<sha>`                   | `<sha>`                   |
| Build configuration | Release, LTO on           | Release, LTO on           |
| Workload            | 10,000 packets            | 10,000 packets            |
| Primary metric      | 42.8 ms                   | 35.1 ms                   |
| Variation           | 42.4–43.2 ms              | 34.9–35.5 ms              |
| Method              | GPIO marker, 100 MS/s     | Same                      |
| Secondary checks    | Tests pass, 184 KiB flash | Tests pass, 186 KiB flash |

## Interpret results conservatively

* A bounded ETM instruction capture is usually the tail of a longer run.
* Streamed ETM repeatedly halts the target and can break timing-sensitive behavior.
* J-Trace coverage and instruction capture reset and run the target; they are not attach-only observations.
* GDB state and post-reset trace are not one uninterrupted execution.
* Software-polled Joulescope GPI windows operate at about 50 ms and cannot prove microsecond timing.
* Separate instruments do not share a clock unless you provide a marker or use an instrument-native combined capture.
* A requested analyzer sample rate can be adjusted to a supported rate; use the achieved value in calculations.
* A different ELF, optimization level, board revision, supply, or workload invalidates a direct comparison.
* Static analyzer performance findings identify code patterns, not measured target speed.

<CardGroup cols={2}>
  <Card title="Live plots" icon="chart-line" href="/core-concepts/plot">
    Record serial telemetry, derive channels, and export measurements.
  </Card>

  <Card title="Power analyzer" icon="bolt" href="/debug-mode/power-analyzer">
    Configure PPK2 and Joulescope measurements safely.
  </Card>

  <Card title="Coverage and trace" icon="timeline" href="/debug-mode/coverage-and-trace">
    Choose RTT, ITM, ETM instruction, or source-coverage evidence.
  </Card>

  <Card title="Combined workflows" icon="diagram-project" href="/debug-mode/combined-workflows">
    Correlate measurements with logs, GPIO markers, and target state.
  </Card>
</CardGroup>
