When does macro expansion take place/use macro inside hot loop?

I have a function inside a nested loop, and I’m cleaning it up with @unpack from UnPack.jl for about 20 parametrs/pre-allocated matrix:

@unpack a,b,c = p #parameters...
@unpack m1,m2,m3 = p #pre-allocated matrices for things like mul!()

@code_llvm suggests that the hand-written version (a=p.a, b=p.b, etc) generates more or less the same code as the cleaner @unpack version, with only a few comments such as

; ┌ @ /Users/user/.julia/packages/UnPack/1IjJI/src/UnPack.jl:101 within `macro expansion'
; │┌ @ /Users/user/.julia/packages/UnPack/1IjJI/src/UnPack.jl:34 within `unpack'

testing with @benchmark suggests there’s very little performance difference. Just to understand the inner workings, is having a macro equivalent to the expanded version from the compiler’s point of view, and there would be no performance penalties to use macros inside the nested/hot loop? Thanks!

JuliaCon 2017 | AoT or JIT: How Does Julia Work? | Jameson Nash - YouTube is very useful for understanding the Julia compiler in general. The specific answer is that macros operate on code_lowered, which happen before the rest of the compiler. As such, macros have 0 performance cost compared to the code they generate.


A slight correction: Macros don’t actually work on the lowered representation but on the abstract syntax tree right after code is parsed and lowering only happens afterwards. What this means is that macros always just work on the raw syntax before any expansion of e.g. dot broadcasting to function calls happen. There is currently no way to retrieve the AST of the source code of functions, but you can inspect the AST of blocks of code with Meta.@dump.