Another use case of macros is to unroll loops into repeating code fragments. Loop unrolling is a performance optimization technique. The premise behind it is that there is always some overhead that is required to execute code in a loop. The reason is that, every time an iteration is finished, the loop must check for a condition and decide whether it should exit or continue with the next iteration. Now, if we know exactly how many times the loop needs to run the code, then we can unroll it by writing out the code in a repeated fashion.
Consider a simple loop as follows:
for i in 1:3
println("hello: ", i)
end
We can unroll the loop into three lines of code that do the exact same job:
println("hello: ", 1)
println("hello: ", 2)
println("hello: ", 3)
But it would be quite a boring and mundane task to have to unroll loops manually. Furthermore, the amount of work grows linearly with the number of iterations required in the loop. With the help of Unroll.jl, we can define a function using the @unroll macro, as follows:
using Unrolled
@unroll function hello(xs)
@unroll for i in xs
println("hello: ", i)
end
end
The code looks as clean as it should be, and the @unroll macro is inserted in front of the function as well as the for loop. First, we should check that the code works properly:
Now, we should question whether the @unroll macro actually did anything. A good way to check whether the loop was unrolled is to use the @code_lowered macro:
The lowered code clearly contains three println statements rather than a single for loop.
Now that we have seen some examples and know the power of metaprogramming, we shall move forward and learn how to create these macros ourselves.