Debugging, testing and benchmarking Julia code
Write test sets, benchmark honestly with BenchmarkTools, find allocations, and trace a running function with the debugger.
Testing with Test.jl
using Test
@testset "Geometry" begin
@test area(Circle(1.0)) ≈ pi
@test perimeter(Circle(2.0)) == 4 * pi
@test_throws MethodError Circle("not a number")
@testset "edge cases" begin
@test isnan(area(Circle(NaN)))
@test area(Circle(0.0)) == 0.0
end
end
# approximate comparison needs the ≈ operator, not ==, for floating point
@test 0.1 + 0.2 ≈ 0.3
@test 0.1 + 0.2 == 0.3 # fails
# run the suite for a package
# julia --project=. -e 'using Pkg; Pkg.test()'@testsetgroups results and reports which ones failed, and nested sets give you a readable hierarchy.- Use
≈(typed as\approxthen tab) for floating-point equality, optionally withatolandrtol. @test_brokendocuments a known failure and turns into a failure if it starts passing, which is a useful reminder.
Benchmarking with BenchmarkTools
using BenchmarkTools
f(x) = sum(abs2, x)
x = rand(1000)
@btime f(x) # multiple samples, reports the minimum
@benchmark f($x) # never use a global name without $
@benchmark f($x) samples = 1000 evals = 1
# inspect allocations
@ballocated f($x)
b = @benchmark f($x)
b.memory # bytes allocated across the batch
b.allocs
# type stability, the usual cause of slowness
@code_warntype f(x)
@code_typed f(x)| Symptom | Likely cause |
|---|---|
| Many allocations in a tight loop | Boxing, type instability, or a temporary array |
| Performance grows with input size | A copy inside the function, or a non-constant global |
| First call slow, later fast | Compilation, not the algorithm |
Type shown as a union or Any | An abstractly typed field or an untyped container |
⚠️
A benchmark that reads global variables measures dynamic dispatch rather than your algorithm, and one that omits the
$ interpolation prints a warning for exactly that reason. Always interpolate inputs with $, and treat the first call as compilation rather than execution.Debugging
using Debugger
@enter f(rand(10)) # step into the call, then use the REPL commands
# n step to the next line
# s step into the next call
# bt show the backtrace
# fr 2 switch to frame 2
# q quit
@run f(rand(10)) # run until an error and stop there
using Infiltrator
function g(x)
@infiltrate x < 0 # drops into a REPL only when the condition holds
sqrt(abs(x))
end- Reproduce the failure as a small script, not through the full pipeline.
- Print types, not just values:
typeof(x)and@code_warntypeanswer most performance questions. - Check method existence with
methods(f)and@whichwhen dispatch picks something unexpected. - Reach for the debugger only after printing has failed to reveal the state.
FAQ
Why is @btime faster than the code in my program?
The benchmark warms up, runs the function many times and reports the fastest sample, so it measures the compiled steady state. Real workloads include dispatch, allocation and I/O that the benchmark excludes.
How do I know whether my code is type stable?
Run
@code_warntype f(args) and look for red entries such as Any or Union in the return type. Fix abstract field types and non-constant globals first; those cause most of the instability seen in practice.Related
Packages, environments and performance Modules and organising a Julia project
Last refreshed 2026-09-18.