Note that you can also just write your own @debug
-like macro.
Parse-time version
When should_print_pt
is set to false
, println
will not be put into the Julia code.
const should_print_pt = false
macro debug_pt(ex)
should_print_pt ? esc(:(println($ex))) : nothing
end
function foo_debug_pt()
@debug_pt "Hello"
return 0
end
function loop_foo_debug_pt()
for _ in 1:1000
foo_debug_pt()
end
end
julia> @code_lowered foo_debug_pt()
CodeInfo(
1 ─ return 0
)
julia> @btime loop_foo_debug_pt() # (No idea why we don't get 1.2 ns like in the loop_foo case on my machine)
16.316 ns (0 allocations: 0 bytes)
julia> @code_llvm loop_foo_debug_pt()
; Function Signature: loop_foo_debug_pt()
; @ REPL[4]:1 within `loop_foo_debug_pt`
; Function Attrs: uwtable
define void @julia_loop_foo_debug_pt_7305() #0 {
top:
; @ REPL[4]:4 within `loop_foo_debug_pt`
ret void
}
The downside is that this is not flexible at all: to set should_print_pt
to true
you have to reload the code.
julia> const should_print_pt = true
WARNING: redefinition of constant Main.should_print_pt. This may fail, cause incorrect answers, or produce other errors.
true
julia> foo_debug_pt() # Does not print
0
julia> macro debug_pt(ex)
should_print_pt ? esc(:(println($ex))) : nothing
end;
julia> function foo_debug_pt()
@debug_pt "Hello"
return 0
end;
julia> foo_debug_pt()
Hello
0
Compile-time version
When should_print_ct
is the false
function, println
will be put into the Julia code, but gets compiled away.
should_print_ct() = false
macro debug_ct(ex)
esc(:((should_print_ct() && println($ex)); nothing))
end
function foo_debug_ct()
@debug_ct "Hello"
return 0
end
function loop_foo_debug_ct()
for _ in 1:1000
foo_debug_ct()
end
end
julia> @code_lowered foo_debug_ct() # Still has a println
CodeInfo(
1 ─ %1 = Main.should_print_ct()
└── goto #3 if not %1
2 ─ Main.println("Hello")
└── goto #3
3 ┄ Main.nothing
└── return 0
)
julia> @btime loop_foo_debug_ct()
1.200 ns (0 allocations: 0 bytes)
julia> @code_llvm loop_foo_debug_ct() # No more println
; Function Signature: loop_foo_debug_ct()
; @ REPL[4]:1 within `loop_foo_debug_ct`
; Function Attrs: uwtable
define void @julia_loop_foo_debug_ct_7774() #0 {
top:
; @ REPL[4]:4 within `loop_foo_debug_ct`
ret void
}
Changing should_print_ct
will automatically trigger recompilation of foo_debug_pt
, making this more flexible, at the cost of recompilations.
julia> foo_debug_ct() # We still have should_print_ct() = false
0
julia> @time begin should_print_ct() = true; foo_debug_ct() end
Hello
0.003950 seconds (751 allocations: 36.633 KiB, 93.69% compilation time: 100% of which was recompilation)
0
Runtime version
The println
will always remain in the compiled code, but will not be executed when !should_print_rt[]
. This will make this version a bit slower.
const should_print_rt = Ref(false)
macro debug_rt(ex)
esc(:((should_print_rt[] && println($ex)); nothing))
end
function foo_debug_rt()
@debug_rt "Hello"
return 0
end
function loop_foo_debug_rt()
for _ in 1:1000
foo_debug_rt()
end
end
julia> @code_llvm foo_debug_rt()
; Function Signature: foo_debug_rt()
; @ REPL[3]:1 within `foo_debug_rt`
; Function Attrs: uwtable
define i64 @julia_foo_debug_rt_7422() #0 {
top:
; @ REPL[3]:2 within `foo_debug_rt`
; ┌ @ refvalue.jl:59 within `getindex`
; │┌ @ Base.jl:49 within `getproperty`
%0 = load i8, ptr @"jl_global#7426.jit", align 16
%1 = and i8 %0, 1
%.x.not = icmp eq i8 %1, 0
; └└
br i1 %.x.not, label %L5, label %L4
L4: ; preds = %top
call void @j_println_7428(ptr nonnull @"jl_global#7429.jit")
br label %L5
L5: ; preds = %L4, %top
; @ REPL[3]:3 within `foo_debug_rt`
ret i64 0
}
julia> @btime loop_foo_debug_rt()
255.251 ns (0 allocations: 0 bytes)
So this is still much faster than the full @debug
, mainly because we’re using a RefValue{Bool}
instead of the ENV
dictionary.
Altering should_print_rt[]
does not have any cost, making this the most flexible.
julia> foo_debug_rt() # should_print_rt[] == false
0
julia> @time begin should_print_rt[] = true; foo_debug_rt() end
Hello
0.000162 seconds (5 allocations: 80 bytes)
0