Do procedural macros just take longer to parse

#[proc_macro_derive(My_Foo)]
pub fn proc_my_foo(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    todo!("hello world")
}

with nothing else takes 0.5s to parse (according to cargo build --timings).

In all other cases, empty crates (regardless of dependencies is generally 0.1s - 0.15s to build).

Does proc_macro_derive just add 0.5s to compile time ? What is it doing ?

I think you might be misinterpreting the output from cargo build --timings.

Given a graph like this

2022-10-24_14-14-21-03

The colour scheme is

  • yellow: build scripts
  • blue: non-parallelisable components
  • purple: parallelisable

Typically, the non-parallelisable part is parsing and typechecking (essentially everything required to generate a *.rmeta file) and the parallelisable part is code generation in LLVM because we don't actually need machine code until the final linking step.

However, because the compiler loads proc-macros with dlopen and runs them at compile time, everything about a proc-macro is "non-parallelisable". Generating machine code and linking everything into a *.so file is slow, so that's probably where most of your 0.5s is going.

"Non-parallelisable" is probably the wrong word here, but basically I mean the parts that block the compiler from starting to build downstream dependencies.

4 Likes

I think you're right. My previous understanding was:

  • blue/cyan = parsing + type checking
  • purple = codegen

However, your claim of:

  • blue/cyan = non-parallelizable / blocks dependencies
  • purple = parallelizable / dependencies can start running

makes more sense.

And in this case, indeed, dependencies of the procedural macro needs to wait until an executable is built before the dependencies can start running.