Skip to content

Commit

Permalink
added btime and belapsed macros, fixed prettymemory to use SI units (#37
Browse files Browse the repository at this point in the history
)

* added btime and belapsed macros, fixed prettymemory to use SI units

* more test output on at-btime test

* more inclusive pattern in btime test

* rm duplication
  • Loading branch information
stevengj authored and jrevels committed Jan 22, 2017
1 parent 83266f1 commit 208cb75
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 33 deletions.
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
julia 0.4
Compat 0.8.0
Compat 0.9.5
JLD 0.6.6
38 changes: 28 additions & 10 deletions doc/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ To quickly benchmark a Julia expression, use `@benchmark`:
```julia
julia> @benchmark sin(1)
BenchmarkTools.Trial:
memory estimate: 0.00 bytes
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 13.00 ns (0.00% GC)
Expand All @@ -77,7 +77,7 @@ julia> tune!(b);

julia> run(b)
BenchmarkTools.Trial:
memory estimate: 0.00 bytes
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 13.00 ns (0.00% GC)
Expand All @@ -91,6 +91,22 @@ BenchmarkTools.Trial:
memory tolerance: 1.00%
```

Alternatively, you can use the `@btime` or `@belapsed` macros.
These take exactly the same arguments as `@benchmark`, but
behave like the `@time` or `@elapsed` macros included with
Julia: `@btime` prints the minimum time and memory allocation
before returning the value of the expression, while `@elapsed`
returns the minimum time in seconds.

```
julia> @btime sin(1)
11.410 ns (0 allocations: 0 bytes)
0.8414709848078965
julia> @belapsed sin(1)
1.1412412412412412e-8
```

### Benchmark `Parameters`

You can pass the following keyword arguments to `@benchmark`, `@benchmarkable`, and `run` to configure the execution process:
Expand Down Expand Up @@ -128,7 +144,7 @@ You can interpolate values into `@benchmark` and `@benchmarkable` expressions:
# rand(1000) is executed for each evaluation
julia> @benchmark sum(rand(1000))
BenchmarkTools.Trial:
memory estimate: 7.92 kb
memory estimate: 7.92 KiB
allocs estimate: 3
--------------
minimum time: 1.68 μs (0.00% GC)
Expand All @@ -145,7 +161,7 @@ BenchmarkTools.Trial:
# value is interpolated into the benchmark expression
julia> @benchmark sum($(rand(1000)))
BenchmarkTools.Trial:
memory estimate: 0.00 bytes
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 185.00 ns (0.00% GC)
Expand All @@ -167,7 +183,7 @@ julia> A = rand(1000);
# BAD: A is a global variable in the benchmarking context
julia> @benchmark [i*i for i in A]
BenchmarkTools.Trial:
memory estimate: 241.63 kb
memory estimate: 241.63 KiB
allocs estimate: 9960
--------------
minimum time: 856.66 μs (0.00% GC)
Expand All @@ -183,7 +199,7 @@ BenchmarkTools.Trial:
# GOOD: A is a constant value in the benchmarking context
julia> @benchmark [i*i for i in $A]
BenchmarkTools.Trial:
memory estimate: 7.89 kb
memory estimate: 7.89 KiB
allocs estimate: 1
--------------
minimum time: 1.12 μs (0.00% GC)
Expand All @@ -197,6 +213,8 @@ BenchmarkTools.Trial:
memory tolerance: 1.00%
```

(Note that "KiB" is the SI prefix for a [kibibyte](https://en.wikipedia.org/wiki/Kibibyte): 1024 bytes.)

Keep in mind that you can mutate external state from within a benchmark:

```julia
Expand Down Expand Up @@ -250,7 +268,7 @@ BenchmarkTools.Benchmark{symbol("##benchmark#7556")}(BenchmarkTools.Parameters(5

julia> run(b)
BenchmarkTools.Trial:
memory estimate: 0.0 bytes
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 6.76 ms (0.0% GC)
Expand All @@ -275,7 +293,7 @@ It's possible for LLVM and Julia's compiler to perform optimizations on `@benchm
```julia
julia> @benchmark (view(a, 1:2, 1:2); 1) setup=(a = rand(3, 3))
BenchmarkTools.Trial:
memory estimate: 0.00 bytes
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 2.537 ns (0.00% GC)
Expand All @@ -294,7 +312,7 @@ Note, however, that this does not mean that `view(a, 1:2, 1:2)` is non-allocatin
```julia
julia> @benchmark view(a, 1:2, 1:2) setup=(a = rand(3, 3))
BenchmarkTools.Trial:
memory estimate: 64.00 bytes
memory estimate: 64 bytes
allocs estimate: 1
--------------
minimum time: 17.079 ns (0.00% GC)
Expand Down Expand Up @@ -330,7 +348,7 @@ Running a benchmark produces an instance of the `Trial` type:
```julia
julia> t = @benchmark eig(rand(10, 10))
BenchmarkTools.Trial:
memory estimate: 18.84 kb
memory estimate: 18.84 KiB
allocs estimate: 70
--------------
minimum time: 167.17 μs (0.00% GC)
Expand Down
4 changes: 3 additions & 1 deletion src/BenchmarkTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ include("execution.jl")
export tune!,
warmup,
@benchmark,
@benchmarkable
@benchmarkable,
@belapsed,
@btime

##########################################
# Plotting Facilities (loaded on demand) #
Expand Down
90 changes: 79 additions & 11 deletions src/execution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@ end
sample(b::Benchmark, args...) = error("no execution method defined on type $(typeof(b))")
_run(b::Benchmark, args...; kwargs...) = error("no execution method defined on type $(typeof(b))")

function Base.run(b::Benchmark, p::Parameters = b.params; kwargs...)
# return (Trial, result) tuple, where result is the result of the benchmarked expression
function run_result(b::Benchmark, p::Parameters = b.params; kwargs...)
return eval(current_module(), :(BenchmarkTools._run($(b), $(p); $(kwargs...))))
end

Base.run(b::Benchmark, p::Parameters = b.params; kwargs...) =
run_result(b, p; kwargs...)[1]

function Base.run(group::BenchmarkGroup, args...; verbose::Bool = false, pad = "", kwargs...)
result = similar(group)
gcscrub() # run GC before running group, even if individual benchmarks don't manually GC
Expand Down Expand Up @@ -202,7 +206,7 @@ macro benchmark(args...)
end)
end

macro benchmarkable(args...)
function benchmarkable_parts(args)
core, params = prunekwargs(args...)

# extract setup/teardown if present, removing them from the original expression
Expand All @@ -228,6 +232,12 @@ macro benchmarkable(args...)
end
end

return core, setup, teardown, params
end

macro benchmarkable(args...)
core, setup, teardown, params = benchmarkable_parts(args)

# extract any variable bindings shared between the core and setup expressions
setup_vars = isa(setup, Expr) ? collectvars(setup) : []
core_vars = isa(core, Expr) ? collectvars(core) : []
Expand Down Expand Up @@ -278,7 +288,8 @@ function generate_benchmark_definition(eval_module, out_vars, setup_vars,
__evals = __params.evals
__gc_start = Base.gc_num()
__start_time = time_ns()
for __iter in 1:__evals
__return_val = $(invocation)
for __iter in 2:__evals
$(invocation)
end
__sample_time = time_ns() - __start_time
Expand All @@ -290,7 +301,7 @@ function generate_benchmark_definition(eval_module, out_vars, setup_vars,
__allocs = Int(fld(__gcdiff.malloc + __gcdiff.realloc +
__gcdiff.poolalloc + __gcdiff.bigalloc,
__evals))
return __time, __gctime, __memory, __allocs
return __time, __gctime, __memory, __allocs, __return_val
end
function BenchmarkTools.sample(b::BenchmarkTools.Benchmark{$(id)},
p::BenchmarkTools.Parameters = b.params)
Expand All @@ -304,15 +315,72 @@ function generate_benchmark_definition(eval_module, out_vars, setup_vars,
params.gctrial && BenchmarkTools.gcscrub()
start_time = time()
trial = BenchmarkTools.Trial(params)
iters = 1
while (time() - start_time) < params.seconds
params.gcsample && BenchmarkTools.gcscrub()
push!(trial, $(samplefunc)(params)...)
iters += 1
iters > params.samples && break
params.gcsample && BenchmarkTools.gcscrub()
s = $(samplefunc)(params)
push!(trial, s[1:end-1]...)
return_val = s[end]
iters = 2
while (time() - start_time) < params.seconds && iters params.samples
params.gcsample && BenchmarkTools.gcscrub()
push!(trial, $(samplefunc)(params)[1:end-1]...)
iters += 1
end
return sort!(trial)
return sort!(trial), return_val
end
BenchmarkTools.Benchmark{$(id)}($(params))
end)
end

######################
# convenience macros #
######################

# These macros provide drop-in replacements for the
# Base.@time and Base.@elapsed macros, which use
# @benchmark but yield only the minimum time.

"""
@belapsed expression [other parameters...]
Similar to the `@elapsed` macro included with Julia,
this returns the elapsed time (in seconds) to
execute a given expression. It uses the `@benchmark`
macro, however, and accepts all of the same additional
parameters as `@benchmark`. The returned time
is the *minimum* elapsed time measured during the benchmark.
"""
macro belapsed(args...)
b = Expr(:macrocall, Symbol("@benchmark"), map(esc, args)...)
:(time(minimum($b))/1e9)
end

"""
@btime expression [other parameters...]
Similar to the `@time` macro included with Julia,
this executes an expression, printing the time
it took to execute and the memory allocated before
returning the value of the expression.
Unlike `@time`, it uses the `@benchmark`
macro, and accepts all of the same additional
parameters as `@benchmark`. The printed time
is the *minimum* elapsed time measured during the benchmark.
"""
macro btime(args...)
tmp = gensym()
_, params = prunekwargs(args...)
tune_expr = hasevals(params) ? :() : :(BenchmarkTools.tune!($(tmp)))
return esc(quote
$(tmp) = BenchmarkTools.@benchmarkable $(args...)
BenchmarkTools.warmup($(tmp))
$(tune_expr)
b, val = BenchmarkTools.run_result($(tmp))
bmin = minimum(b)
a = allocs(bmin)
println(" ", BenchmarkTools.prettytime(BenchmarkTools.time(bmin)),
" ($a allocation", a == 1 ? "" : "s", ": ",
BenchmarkTools.prettymemory(BenchmarkTools.memory(bmin)), ")")
val
end)
end
8 changes: 4 additions & 4 deletions src/trials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,13 @@ end

function prettymemory(b)
if b < 1024
value, units = b, "bytes"
return string(b, " bytes")
elseif b < 1024^2
value, units = b / 1024, "kb"
value, units = b / 1024, "KiB"
elseif b < 1024^3
value, units = b / 1024^2, "mb"
value, units = b / 1024^2, "MiB"
else
value, units = b / 1024^3, "gb"
value, units = b / 1024^3, "GiB"
end
return string(@sprintf("%.2f", value), " ", units)
end
Expand Down
26 changes: 26 additions & 0 deletions test/ExecutionTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module ExecutionTests

using Base.Test
using BenchmarkTools
using Compat
import Compat.String

seteq(a, b) = length(a) == length(b) == length(intersect(a, b))

Expand Down Expand Up @@ -151,4 +153,28 @@ tune!(b)
[u^2 for u in [1,2,3]]
end)

# this should take < 1 s on any sane machine
@test @belapsed(sin($(foo.x)), evals=3, samples=10, setup=(foo.x = 0)) < 1

let fname = tempname()
try
ret = open(fname, "w") do f
redirect_stdout(f) do
x = 1
y = @btime(sin($x))
@test y == sin(1)
end
end
s = readstring(fname)
try
@test ismatch(r"[0-9.]+ \w*s \([0-9]* allocations?: [0-9]+ bytes\)", s)
catch
println(STDERR, "@btime output didn't match ", repr(s))
rethrow()
end
finally
isfile(fname) && rm(fname)
end
end

end # module
12 changes: 6 additions & 6 deletions test/TrialsTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ tj_r_2 = judge(tr; time_tolerance = 2.0, memory_tolerance = 2.0)
@test BenchmarkTools.prettytime(999_999_999) == "1000.000 ms"
@test BenchmarkTools.prettytime(1_000_000_000) == "1.000 s"

@test BenchmarkTools.prettymemory(1023) == "1023.00 bytes"
@test BenchmarkTools.prettymemory(1024) == "1.00 kb"
@test BenchmarkTools.prettymemory(1048575) == "1024.00 kb"
@test BenchmarkTools.prettymemory(1048576) == "1.00 mb"
@test BenchmarkTools.prettymemory(1073741823) == "1024.00 mb"
@test BenchmarkTools.prettymemory(1073741824) == "1.00 gb"
@test BenchmarkTools.prettymemory(1023) == "1023 bytes"
@test BenchmarkTools.prettymemory(1024) == "1.00 KiB"
@test BenchmarkTools.prettymemory(1048575) == "1024.00 KiB"
@test BenchmarkTools.prettymemory(1048576) == "1.00 MiB"
@test BenchmarkTools.prettymemory(1073741823) == "1024.00 MiB"
@test BenchmarkTools.prettymemory(1073741824) == "1.00 GiB"

end # module

0 comments on commit 208cb75

Please sign in to comment.