Define a macro based on @timeit from TimerOutputs.jl

Hi everyone,

I am using TimerOutputs.jl to time parts of my code with the @timeit macro. The typical syntax looks like this:

using TimerOutputs
timer = TimerOutput()

@timeit timer "some_identifier" begin
    # some code...
end

I would like to create a new macro called @my_timeit that behaves as follows:

  • Uses @timeit from TimerOutputs.jl when timer is a TimerOutput instance.
  • Executes the code block directly (without timing) if timer == nothing.

My questions are:

  1. Is it possible to define such a macro in Julia?
  2. If it is, would calling @my_timeit nothing "some_identifier" begin ... end add any overhead to the execution of somecode...?

Thanks in advance for any guidance or suggestions!

There was a proposal for adding something like this to TimerOutputs.jl a few years ago, but it was never merged. But defining a separate macro as you propose may also work, I haven’t tried that.

If I need to time a function I can simply use a wrapper like this:

import TimerOutputs

function wrap_with_timer(f::Function, name::String, timer::Union{TimerOutput,Nothing})
    if isnothing(timer)
        return f
    else
        f_(args...; kwargs...) = @timeit timer name f(args...; kwargs...)
        return f_
    end
end

My problem is now just with blocks of codes (which I don’t want to convert to a function)

This seems to work:

macro my_timeit(timer, label, expr)
    quote
        if $(esc(timer)) === nothing
            $(esc(expr))
        else
            @timeit $(esc(timer)) $(esc(label)) $(esc(expr))
        end
    end
end

The branch seems to be optimised away by the compiler, so using timer = nothing is as fast as simply running the expression without @timeit.

Thanks, it works perfectly! Any way to extend it to function definitions without my wrapper (the wrapper above creates problem with @my_timeit)?

Not sure if I understood correctly, but the following variant allows annotating functions (there might be better ways of doing this):

macro my_timeit(timer, f)
    f.head === :function || error("expected a function")
    call = f.args[1]
    @assert call.head === :call   # e.g. :(f(x))
    label = string(call.args[1])  # e.g. "f"
    expr = f.args[2]  # actual content of the function
    @assert expr.head === :block
    # Replace function content
    f.args[2] = quote
        if $timer === nothing
            $expr
        else
            @timeit $timer $label $expr
        end
    end
    f
end

This can be used as:

@my_timeit timer function f(x, y, ..., timer)
    # stuff...
end