Skip to content

Commit ae1f8a7

Browse files
authored
Automatically create nested BenchmarkGroup on access (#309)
* Create key when accessing * Export `clear_empty!` command for BenchmarkGroup * Describe use of `clear_empty!` * Remove redundant `clear_empty!` * Add test for EasyConfig-style BenchmarkGroups * Fix GroupsTests.jl * Fix docs * Test tuple-based indexing also eagerly creates groups * Add docstrings to `clear_empty!` and `isempty` * Test setindex! for tuple keys
1 parent c1aad4f commit ae1f8a7

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

docs/src/manual.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,19 @@ julia> suite
547547
As you might imagine, `BenchmarkGroup` supports a subset of Julia's `Associative` interface. A full list of
548548
these supported functions can be found [in the reference document](reference.md#benchmarkgrouptagsvector-datadict).
549549

550+
One can also create a nested `BenchmarkGroup` simply by indexing the keys:
551+
552+
```julia
553+
suite2 = BenchmarkGroup()
554+
555+
suite2["my"]["nested"]["benchmark"] = @benchmarkable sum(randn(32))
556+
```
557+
558+
which will result in a hierarchical benchmark without us needing to create the `BenchmarkGroup` at each level ourselves.
559+
560+
Note that keys are automatically created upon access, even if a key does not exist. Thus, if you wish
561+
to empty the unused keys, you can use `clear_empty!(suite)` to do so.
562+
550563
### Tuning and running a `BenchmarkGroup`
551564

552565
Similarly to individual benchmarks, you can `tune!` and `run` whole `BenchmarkGroup` instances (following from the previous section):

src/BenchmarkTools.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export BenchmarkGroup,
5555
addgroup!,
5656
leaves,
5757
@benchmarkset,
58-
@case
58+
@case,
59+
clear_empty!
5960

6061
######################
6162
# Execution Strategy #

src/groups.jl

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,43 @@ function addgroup!(suite::BenchmarkGroup, id, args...)
2323
return g
2424
end
2525

26+
"""
27+
clear_empty!(group::BenchmarkGroup)
28+
29+
Recursively remove any empty subgroups from `group`.
30+
31+
Use this to prune a `BenchmarkGroup` after accessing the incorrect
32+
fields, such as `g=BenchmarkGroup(); g[1]`, without storing
33+
anything to `g[1]`, which will create an empty subgroup `g[1]`.
34+
"""
35+
function clear_empty!(group::BenchmarkGroup)
36+
for (k, v) in pairs(group)
37+
if v isa BenchmarkGroup && isempty(v)
38+
delete!(group, k)
39+
end
40+
end
41+
group
42+
end
43+
clear_empty!(x) = x
44+
2645
# Dict-like methods #
2746
#-------------------#
2847

2948
Base.:(==)(a::BenchmarkGroup, b::BenchmarkGroup) = a.tags == b.tags && a.data == b.data
3049
Base.copy(group::BenchmarkGroup) = BenchmarkGroup(copy(group.tags), copy(group.data))
3150
Base.similar(group::BenchmarkGroup) = BenchmarkGroup(copy(group.tags), empty(group.data))
32-
Base.isempty(group::BenchmarkGroup) = isempty(group.data)
51+
52+
"""
53+
isempty(group::BenchmarkGroup)
54+
55+
Return `true` if `group` is empty. This will first
56+
run `clear_empty!` on `group` to recursively remove any empty subgroups.
57+
"""
58+
Base.isempty(group::BenchmarkGroup) = isempty(clear_empty!(group).data)
59+
3360
Base.length(group::BenchmarkGroup) = length(group.data)
34-
Base.getindex(group::BenchmarkGroup, k) = getindex(group.data, makekey(k))
35-
Base.getindex(group::BenchmarkGroup, k...) = getindex(group.data, makekey(k))
61+
Base.getindex(group::BenchmarkGroup, k) = get!(group.data, makekey(k), BenchmarkGroup())
62+
Base.getindex(group::BenchmarkGroup, k...) = get!(group.data, makekey(k), BenchmarkGroup())
3663
Base.setindex!(group::BenchmarkGroup, v, k) = setindex!(group.data, v, makekey(k))
3764
Base.setindex!(group::BenchmarkGroup, v, k...) = setindex!(group.data, v, makekey(k))
3865
Base.delete!(group::BenchmarkGroup, k) = delete!(group.data, makekey(k))

test/GroupsTests.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,52 @@ g1["c"] = tc
324324
"c" => TrialEstimate(1.000 ns)
325325
"""
326326

327+
# EasyConfig-style benchmark groups #
328+
#-----------------------------------#
329+
330+
g1 = BenchmarkGroup()
331+
for T in [Float32, Float64], n in [10, 100], m in [5, 20]
332+
g1["sum"][T][n][m] = @benchmarkable sum(x) setup=(x=randn($T, $n, $m))
333+
end
334+
335+
# Test that the groups were created:
336+
for T in [Float32, Float64], n in [10, 100], m in [5, 20]
337+
@test "sum" in keys(g1.data)
338+
@test string(T) in keys(g1["sum"].data)
339+
@test n in keys(g1["sum"][T].data)
340+
@test m in keys(g1["sum"][T][n].data)
341+
@test typeof(g1["sum"][T][n][m]) == BenchmarkTools.Benchmark
342+
end
343+
344+
# Expected side effect is that accessing groups creates them:
345+
g1["ssum"]
346+
@test "ssum" in keys(g1.data)
347+
g1["ssum2"][Int32]
348+
@test "ssum2" in keys(g1.data)
349+
@test "Int32" in keys(g1["ssum2"].data)
350+
351+
# So we can clear the empty groups with `clear_empty!`:
352+
clear_empty!(g1)
353+
354+
# Now it is clean
355+
@test !("ssum" in keys(g1.data))
356+
@test !("ssum2" in keys(g1.data))
357+
358+
# Likewise with multi-key groups:
359+
g1[1, 2, 3][1, 2, 3][1, 2, 3] = BenchmarkGroup()
360+
@test (1, 2, 3) in keys(g1.data)
361+
@test (1, 2, 3) in keys(g1[1, 2, 3].data)
362+
clear_empty!(g1)
363+
@test !((1, 2, 3) in keys(g1.data))
364+
365+
# But other groups should still be present:
366+
for T in [Float32, Float64], n in [10, 100], m in [5, 20]
367+
@test "sum" in keys(g1.data)
368+
@test string(T) in keys(g1["sum"].data)
369+
@test n in keys(g1["sum"][T].data)
370+
@test m in keys(g1["sum"][T][n].data)
371+
@test typeof(g1["sum"][T][n][m]) == BenchmarkTools.Benchmark
372+
end
373+
374+
327375
# end # module

0 commit comments

Comments
 (0)