Skip to main content
Compiler hints and tips on using the optimization levels smartly in Keil MDK/DS toolset., featured image

Compiler hints and tips on using the optimization levels smartly in Keil MDK/DS toolset.

GSAS Engineering · · 4 min read

The Arm Compiler optimizes your code for small code size and high performance. The trade-off between binary footprint and execution speed is one of the most consequential settings on a Cortex-M project, and the right answer depends on which build you’re producing.

Arm Compiler 6 optimization levels at a glance

Arm Compiler 6 (armclang) is the LLVM-based toolchain that ships with Keil MDK and Arm Development Studio. It exposes six optimization levels, each tuned for a different point on the size/speed/debuggability curve. Pick the level that matches the job of the build, not a vague preference for “faster code.”

LevelWhen to useTrade-off
-O0Debug builds, single-step trace, breakpoint-heavy bring-upLargest binary, slowest runtime, full source-line fidelity
-O1Light optimization with most debug info preservedSome local variables become unavailable in the debugger
-O2Default for releaseBest general-purpose balance of speed and size
-O3Performance-critical loops, vectorizable DSP kernelsLarger binary; aggressive inlining can hurt I-cache hit rates on Cortex-M7
-OsCode-size optimized while keeping reasonable speedSlightly slower than -O2 on hot paths
-OzSmallest possible binary, e.g. Cortex-M0+ with 32 KB flashMost aggressive size optimization; can disable inlining that -Os keeps

Source: Arm Compiler armclang Reference Guide, -O options and the Arm Compiler 6 User Guide.

Function-level optimization with __attribute__

A single -O flag on the project applies to every translation unit. That is rarely the right answer for firmware, you usually want one or two hot functions tuned aggressively while the rest of the image stays size-optimized. armclang accepts the GCC-compatible __attribute__((optimize("O3"))) on individual functions, and #pragma clang optimize on/off for bracketed regions:

__attribute__((optimize("O3")))
void fir_filter(const int16_t *in, int16_t *out, size_t n) {
    /* hot DSP loop, compile at O3 even if the project is -Oz */
}

This pattern keeps the global build at -Os or -Oz for footprint while letting compute kernels run at -O3.

When -Oz pays off and when it hurts

-Oz is the right default for memory-constrained MCUs, Cortex-M0+ parts with 32–64 KB flash, BLE peripherals, sensor nodes. On a Cortex-M7 running an FFT or motor-control loop, -Oz will suppress the inlining and loop unrolling those kernels depend on, and you will measure the difference on a scope. Use -Oz globally with per-function __attribute__((optimize("O3"))) overrides on the kernels that matter, rather than flipping the whole project to -O3.

Linker dead-code elimination pairs with the compiler choice

Compiler optimization only goes as far as the translation unit. To strip unused functions and data at link time, compile with -ffunction-sections -fdata-sections so each symbol lands in its own section, then link with armlink --remove (or armclang -Wl,--gc-sections when invoking the linker via the compiler driver). The Arm linker’s unused section elimination walks the call graph from the entry point and discards anything unreachable. This pairing, fine-grained sections at compile time, garbage collection at link time, typically recovers 5–15 % of image size on a real firmware build before you change a single line of C.

For end-to-end toolchain details and the latest armclang release notes, see the Arm Compiler 6 product page and the Keil MDK page on developer.arm.com.

LEARN MORE ABOUT Arm Tools

Also appears in:

Interested in Arm tools?

Talk to our application engineers for personalized tool recommendations.

Stay in the Loop

Get monthly compliance updates, product insights, and engineering best practices delivered to your inbox.

Related Articles

Master and slave roles on a 100BASE-T1 link: the master PHY times its transmitter from a local clock, the slave recovers the clock from the received signal, with the both-master and both-slave misconfigurations that leave the link down, from GSAS Micro Systems India
Automotive Ethernet Automotive & Mobility

100BASE-T1 Link Won't Come Up: A Vendor-Neutral Checklist

A 100BASE-T1 link that will not come up is almost never a mystery, but the answers on the web are written per silicon vendor and do not transfer. This is the ordered bring-up checklist that holds regardless of which PHY, switch or SoC you have: physical layer first, then the PHY over MDIO, then the master and slave pairing, then the causes of a link that comes up and drops. The standards and tooling claims trace to IEEE 802.3 task force records, the Linux ethtool and kernel documentation or published test material. Written by the GSAS Micro Systems engineering team in India.

29 Aug 2026 · 14 min read
Side by side comparison of a 10BASE-T1S multidrop mixing segment, one balanced pair with four nodes on short stubs and a termination at each end, against a point to point star of four separate links into switch ports, from GSAS Micro Systems India
Automotive Ethernet Automotive & Mobility

10BASE-T1S and PLCA: Multidrop Ethernet Explained

10BASE-T1S is the one member of the T1 single-pair Ethernet family that keeps a shared medium, and PLCA is the reconciliation sublayer that stops the nodes on it from colliding. This article covers what IEEE 802.3cg standardises, how the beacon and transmit opportunities schedule a cycle, the node count and segment length figures the OPEN Alliance interoperability test suite works to, and the failure modes that put a segment quietly back into contention while every link still looks up. Written by the GSAS Micro Systems engineering team in India for teams bringing up multidrop segments on the bench.

29 Aug 2026 · 12 min read
The AUTOSAR network management state ring: Bus Sleep, Repeat Message, Normal Operation, Ready Sleep and Prepare Bus Sleep, with the wake transition marked; the governing timers for each exit are named in the article's sourced state table, from GSAS Micro Systems India
Automotive Ethernet Automotive & Mobility

ECU Testing over Ethernet: Startup, Shutdown and Wake

A bench that powers the ECU, waits for it to settle and then starts capturing has already skipped three of the four phases where integration defects live. This is the lifecycle version of ECU testing over Ethernet: what a ping does not prove, the AUTOSAR network management state set and the timers that govern it read out of the public specifications, why one node holds a whole cluster awake, and a phase-by-phase table of stimulus, observable, pass criterion and capture method you can write test cases from this week. Written by the GSAS Micro Systems engineering team in India.

29 Aug 2026 · 13 min read