Skip to main content

Optimization

Detailed guide to Zig’s optimization capabilities, compiler flags, and performance tuning strategies.

Optimization Modes

Zig’s optimization modes are set with -O and determine the overall compilation strategy. See Compilation Modes for details.
-O Debug          # No optimizations, full safety
-O ReleaseSafe    # Optimized with safety checks
-O ReleaseFast    # Maximum performance, no safety
-O ReleaseSmall   # Optimized for binary size

Compiler Backend Selection

LLVM Backend

The LLVM backend provides the most mature and optimized code generation.
-fllvm            # Force LLVM backend
-fno-llvm         # Prevent LLVM backend
-flibllvm         # Use LLVM API
-fno-libllvm      # Don't use LLVM API
zig build-exe -O ReleaseFast -fllvm main.zig

C Backend

-fclang           # Force Clang for C/C++ code
-fno-clang        # Prevent using Clang

Code Generation Options

Function Sections

Places each function in a separate section, enabling better dead code elimination.
-ffunction-sections      # Each function in separate section
-fno-function-sections   # All functions in same section
Useful with --gc-sections linker flag:
zig build-exe -ffunction-sections --gc-sections main.zig

Data Sections

-fdata-sections      # Each data item in separate section
-fno-data-sections   # All data in same section

Position Independent Code (PIC)

-fPIC         # Force Position Independent Code
-fno-PIC      # Disable PIC
-fPIE         # Position Independent Executable
-fno-PIE      # Disable PIE
zig build-lib -dynamic -fPIC mylib.zig
Requires LLVM extensions.
-flto         # Full LTO
-fno-lto      # Disable LTO (default)
LTO modes:
zig build-exe -O ReleaseFast -flto main.zig
# or explicit:
zig build-exe -O ReleaseFast -flto=full main.zig

LTO Trade-offs

ModeCompile TimeBinary SizePerformance
No LTOFastLargerGood
Thin LTOMediumMediumBetter
Full LTOSlowSmallestBest
Thin LTO provides a good balance - most of the performance benefits with faster compile times than full LTO.

Frame Pointer

-fomit-frame-pointer      # Omit frame pointer (optimization)
-fno-omit-frame-pointer   # Keep frame pointer (debugging)
Omitting frame pointer:
  • Frees up a register for optimizations
  • Makes stack unwinding harder
  • Can improve performance by ~1-2%
Keeping frame pointer:
  • Better stack traces and profiling
  • Required by some debugging tools
  • Recommended for production when debugging needed

Stack Features

Stack Checking

-fstack-check         # Enable stack probing
-fno-stack-check      # Disable stack probing
Detects stack overflows at runtime (small performance cost).

Stack Protection

-fstack-protector     # Enable stack canaries
-fno-stack-protector  # Disable stack protection
Protects against stack buffer overflows with security canaries.

Red Zone

-mred-zone      # Enable red zone optimization
-mno-red-zone   # Disable red zone
The red zone is an optimization where leaf functions can use stack space beyond the stack pointer without adjusting it. Required to be disabled for interrupt handlers.

CPU-Specific Options

Target CPU

-mcpu [cpu]         # Specify target CPU and features
zig build-exe -mcpu=native main.zig
Optimizes for the current CPU.

Code Model

-mcmodel=[model]    # Set code model
Available models:
  • tiny - Smallest code model
  • small - Default for most targets
  • kernel - Kernel code
  • medium - Medium-sized programs
  • large - Large programs
  • default - Target-specific default
zig build-exe -mcmodel=kernel kernel.zig

Debug Information

Strip Debug Info

-fstrip         # Remove debug symbols
-fno-strip      # Keep debug symbols (default in Debug)
Stripping reduces binary size by 30-70% but makes debugging impossible.

DWARF Format

-gdwarf32       # Use 32-bit DWARF
-gdwarf64       # Use 64-bit DWARF

Debug Compression

--compress-debug-sections=[format]
Formats:
  • none - No compression
  • zlib - zlib compression (default)
  • zstd - Zstandard compression (better ratio)
zig build-exe --compress-debug-sections=none main.zig

Unwind Tables

-funwind-tables        # Sync unwind tables
-fasync-unwind-tables  # Async unwind tables
-fno-unwind-tables     # No unwind tables
zig c++ -funwind-tables program.cpp

Sanitizers

UndefinedBehaviorSanitizer (C code)

-fsanitize-c[=mode]    # Enable UBSan for C
-fno-sanitize-c        # Disable UBSan
Modes:
  • trap - Insert trap instructions
  • full - Insert runtime calls (default)
zig build-exe -fsanitize-c=trap main.zig helper.c

ThreadSanitizer

-fsanitize-thread      # Enable ThreadSanitizer
-fno-sanitize-thread   # Disable ThreadSanitizer
Detects data races in multithreaded code:
zig build-exe -fsanitize-thread server.zig

Valgrind Support

-fvalgrind          # Include Valgrind client requests
-fno-valgrind       # Omit Valgrind requests
Useful for memory debugging:
zig build-exe -O ReleaseSafe -fvalgrind main.zig
valgrind ./main

Linker Optimizations

Garbage Collection

--gc-sections       # Remove unused code/data
--no-gc-sections    # Keep all sections
Combine with -ffunction-sections and -fdata-sections for best results.
zig build-exe -ffunction-sections -fdata-sections --gc-sections main.zig

Dead Code Elimination (macOS)

-dead_strip         # Remove unused code (macOS)
-dead_strip_dylibs  # Remove unused libraries (macOS)

Symbol Binding

-Bsymbolic    # Bind global references locally
Reduces symbol lookup overhead in shared libraries.

LLVM-Specific Options

Optimization Pass Limit

-fopt-bisect-limit=[N]    # Only run first N optimization passes
Useful for debugging optimization-related bugs.
zig build-exe -O ReleaseFast -fopt-bisect-limit=100 main.zig

Incremental Compilation

-fincremental       # Enable incremental compilation
-fno-incremental    # Disable incremental compilation
Incremental compilation can significantly speed up rebuilds during development.

Builtin Functions

-fbuiltin          # Enable implicit builtin knowledge
-fno-builtin       # Disable builtin knowledge
Affects C/C++ builtins like memcpy, strlen, etc.

Single-Threaded

-fsingle-threaded       # Code assumes single thread
-fno-single-threaded    # Multi-threaded code
zig build-exe -fsingle-threaded embedded.zig
Benefits:
  • No atomic operations overhead
  • Smaller binaries
  • Simpler code generation

Error Tracing

-ferror-tracing       # Enable error return traces
-fno-error-tracing    # Disable error traces
Error traces show the path errors take through the program. Disabled in ReleaseFast/Small by default.

Reference Tracing

-freference-trace[=N]    # Show N lines of reference trace per error
-fno-reference-trace     # Disable reference traces
zig build-exe -freference-trace=10 main.zig

Fuzzing

-ffuzz           # Enable fuzzing instrumentation
-fno-fuzz        # Disable fuzzing
For use with fuzzing tools like AFL or libFuzzer.

DLL Export

-fdll-export-fns       # Mark exported functions as DLL exports (Windows)
-fno-dll-export-fns    # Don't mark as DLL exports

Optimization Strategy Examples

Maximum Performance

zig build-exe \
  -O ReleaseFast \
  -fllvm \
  -flto=thin \
  -mcpu=native \
  -ffunction-sections \
  --gc-sections \
  -fomit-frame-pointer \
  main.zig

Minimum Binary Size

zig build-exe \
  -O ReleaseSmall \
  -fstrip \
  -ffunction-sections \
  -fdata-sections \
  --gc-sections \
  -fno-unwind-tables \
  main.zig

Balanced Production

zig build-exe \
  -O ReleaseSafe \
  -fllvm \
  -fno-strip \
  -ferror-tracing \
  --compress-debug-sections=zstd \
  main.zig

Debug Build

zig build-exe \
  -O Debug \
  -fno-strip \
  -ferror-tracing \
  -freference-trace=256 \
  main.zig

Performance Profiling

Before optimizing compilation flags, profile your code to find actual bottlenecks.

Time Report

--time-report    # Show compilation time breakdown
Sends timing diagnostics when using --listen.

Stack Report

-fstack-report   # Show stack size diagnostics

Benchmarking

const std = @import("std");
const time = std.time;

pub fn main() !void {
    const start = try time.Instant.now();
    
    // Your code here
    benchmark();
    
    const end = try time.Instant.now();
    const elapsed = end.since(start);
    
    std.debug.print("Time: {d}ms\n", .{elapsed / time.ns_per_ms});
}

Best Practices

Profile First

Use profilers to find real bottlenecks before changing compiler flags.

Start Simple

Begin with -O ReleaseSafe, only move to ReleaseFast if needed.

Measure Impact

Benchmark before and after optimization flag changes.

Test Thoroughly

Always test optimized builds - optimizations can expose bugs.
  1. Algorithm matters more - A better algorithm beats compiler optimizations
  2. Measure, don’t guess - Profile to find real bottlenecks
  3. Safety first - Use ReleaseSafe unless profiling proves it’s too slow
  4. Test optimizations - Ensure optimized builds work correctly
  5. Document choices - Explain why specific optimization flags are used

Next Steps

Debugging

Debug optimized and unoptimized builds

Compilation Modes

Understand Debug, ReleaseSafe, ReleaseFast, ReleaseSmall

Build docs developers (and LLMs) love