Course contentsShow
← Rust
Lesson 1550 of 1,91013. Metaprogramming with MacrosFree lesson

Benchmarking Macro Compilation

You will profile the impact of procedural macros on overall project build times.

In this lesson, you will learn how to quantify the build-time overhead introduced by your procedural macros to ensure they remain performant as your project scales.

The Macro Tax

Procedural macros run during the compilation process, effectively acting as "compile-time programs." Because they parse TokenStreams, perform logic, and emit new code, they can significantly inflate compilation times, especially in large crates with thousands of macro expansions. When a macro is complex, it doesn't just run once; it runs for every single usage, potentially creating a bottleneck that hinders your developer experience.

Measuring the Impact

The standard tool for diagnosing this is the built-in compiler timing instrumentation. You can view exactly how long the compiler spends expanding your macros by running cargo with the build-plan or timings flag:

cargo build --timings

This command generates an HTML report (usually in target/cargo-timings/) that visualizes the build process. Look specifically for the "macro expansion" phases in the dependency graph. If your macro-heavy crate shows a long duration compared to your pure-library crates, you’ve identified a performance hotspot.

Refinement Strategies

If you find that your macro is a bottleneck, you don't necessarily need to discard it. Instead, you can profile the internal logic of your procedural macro crate just like any other Rust code:

  1. Unit Testing Expansion: Create a small test suite that uses proc_macro2 to parse a large input TokenStream and measure the execution time of your transformation logic using std::time::Instant.
  2. Minimize Work: Check if your macro is re-parsing inputs unnecessarily. Since macros run as part of the compiler process, any inefficiency here is multiplied across your entire codebase.
  3. Caching: Ensure that your macro output is deterministic. While rustc handles much of this, avoiding complex logic that performs heavy I/O or unnecessary heap allocations inside the proc_macro function will keep the compiler's memory footprint—and your compile times—under control.

By benchmarking these expansions, you move from guessing why a project is slow to making data-driven decisions about when to optimize a macro versus when to refactor it into standard trait-based code.

Key Takeaway: Use cargo build --timings to identify macro-induced bottlenecks and treat your procedural macro's execution logic as a performance-critical hot path.