Skip to content

Commit e526c5d

Browse files
committed
Add LinuxPerf extension for branch + instruction counts
This updates the core BenchmarkTools types to include `instructions` and `branches` fields. If the extension is not available, these are `NaN`. No support is included for measuring overhead or making judgements based on these fields, but Serialization, Statistics, etc. are all supported with their usual functionality for Trial / TrialEstimate / etc.
1 parent cb09e40 commit e526c5d

File tree

14 files changed

+419
-65
lines changed

14 files changed

+419
-65
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,14 @@ jobs:
1616
fail-fast: false
1717
matrix:
1818
version:
19-
- '1.6'
19+
- '1.10'
2020
- '1'
2121
- 'nightly'
2222
arch:
2323
- x64
2424
os:
2525
- ubuntu-latest
2626
include:
27-
- version: '1.7'
28-
arch: x64
29-
os: ubuntu-20.04
30-
- version: '1.8'
31-
arch: x64
32-
os: ubuntu-22.04
3327
- version: '1.9'
3428
arch: x64
3529
os: ubuntu-22.04

Project.toml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "BenchmarkTools"
22
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3-
version = "1.6.0"
3+
version = "1.7.0"
44

55
[deps]
66
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
@@ -11,6 +11,12 @@ Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
1111
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1212
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
1313

14+
[weakdeps]
15+
LinuxPerf = "b4c46c6c-4fb0-484d-a11a-41bc3392d094"
16+
17+
[extensions]
18+
LinuxPerfExt = "LinuxPerf"
19+
1420
[compat]
1521
Aqua = "0.8"
1622
Compat = ">= 4.11.0"
@@ -22,7 +28,8 @@ Profile = "<0.0.1, 1"
2228
Statistics = "<0.0.1, 1"
2329
Test = "<0.0.1, 1"
2430
UUIDs = "<0.0.1, 1"
25-
julia = "1.6"
31+
julia = "1.9"
32+
LinuxPerf = ">= 0.4"
2633

2734
[extras]
2835
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
@@ -31,4 +38,4 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
3138
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3239

3340
[targets]
34-
test = ["Aqua", "JuliaFormatter", "Statistics", "Test"]
41+
test = ["Aqua", "JuliaFormatter", "Statistics", "Test", "LinuxPerf"]

ext/LinuxPerfExt/LinuxPerfExt.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module LinuxPerfExt
2+
3+
import BenchmarkTools: PerfInterface
4+
import LinuxPerf: LinuxPerf, PerfBench, EventGroup, EventType
5+
import LinuxPerf: enable!, disable!, enable_all!, disable_all!, close, read!
6+
7+
function interface()
8+
return PerfInterface(;
9+
setup=() -> PerfBench(
10+
0, [EventGroup([EventType(:hw, :instructions), EventType(:hw, :branches)])]
11+
),
12+
start=(bench) -> enable_all!(),
13+
stop=(bench) -> disable_all!(),
14+
# start=(bench) -> enable!(bench),
15+
# stop=(bench) -> disable!(bench),
16+
teardown=(bench) -> close(bench),
17+
read=(bench) -> let g = only(bench.groups)
18+
(insts, branches) = read!(g.leader_io, Vector{UInt64}(undef, 5))
19+
return (insts, branches)
20+
end,
21+
)
22+
end
23+
24+
end

src/BenchmarkTools.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export loadparams!
2525
include("trials.jl")
2626

2727
export gctime,
28+
instructions,
29+
branches,
2830
memory,
2931
allocs,
3032
params,

src/execution.jl

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,24 @@ macro benchmarkable(args...)
506506
end
507507
end
508508

509+
struct PerfInterface
510+
setup::Function
511+
start::Function
512+
stop::Function
513+
read::Function
514+
teardown::Function
515+
516+
function PerfInterface(;
517+
setup=Returns(nothing),
518+
start=Returns(nothing),
519+
stop=Returns(nothing),
520+
read=Returns((-1, -1)),
521+
teardown=Returns(nothing),
522+
)
523+
return new(setup, start, stop, read, teardown)
524+
end
525+
end
526+
509527
# `eval` an expression that forcibly defines the specified benchmark at
510528
# top-level in order to allow transfer of locally-scoped variables into
511529
# benchmark scope.
@@ -553,6 +571,8 @@ function generate_benchmark_definition(
553571
end
554572
)
555573
end
574+
ext = Base.get_extension(BenchmarkTools, :BenchmarkToolsLinuxPerfExt)
575+
LinuxPerf = isnothing(ext) ? PerfInterface() : ext.interface()
556576
return Core.eval(
557577
eval_module,
558578
quote
@@ -563,17 +583,42 @@ function generate_benchmark_definition(
563583
$(Expr(:tuple, quote_vars...)), __params::$BenchmarkTools.Parameters
564584
)
565585
$(setup)
586+
__perf_bench = $(LinuxPerf.setup)()
587+
__gcdiff = nothing
588+
__return_val = nothing
589+
__sample_time::Int64 = 0
590+
__sample_instructions::Int64 = 0
591+
__sample_branches::Int64 = 0
566592
__evals = __params.evals
567-
__gc_start = Base.gc_num()
568-
__start_time = time_ns()
569-
__return_val = $(invocation)
570-
for __iter in 2:__evals
571-
$(invocation)
593+
try
594+
__gc_start = Base.gc_num()
595+
$(LinuxPerf.start)(__perf_bench)
596+
__start_time = time_ns()
597+
__return_val = $(invocation)
598+
for __iter in 2:__evals
599+
$(invocation)
600+
end
601+
__sample_time = time_ns() - __start_time
602+
$(LinuxPerf.stop)(__perf_bench)
603+
__gcdiff = Base.GC_Diff(Base.gc_num(), __gc_start)
604+
__sample_instructions, __sample_branches = $(LinuxPerf.read)(
605+
__perf_bench
606+
)
607+
finally
608+
$(LinuxPerf.teardown)(__perf_bench)
609+
$(teardown)
572610
end
573-
__sample_time = time_ns() - __start_time
574-
__gcdiff = Base.GC_Diff(Base.gc_num(), __gc_start)
575-
$(teardown)
576611
__time = max((__sample_time / __evals) - __params.overhead, 0.001)
612+
__instructions = if (__sample_instructions == -1)
613+
NaN
614+
else
615+
max((__sample_instructions / __evals) - __params.insts_overhead, 0.0)
616+
end
617+
__branches = if (__sample_branches == -1)
618+
NaN
619+
else
620+
max((__sample_branches / __evals) - 0.0, 0.0)
621+
end
577622
__gctime = max((__gcdiff.total_time / __evals) - __params.overhead, 0.0)
578623
__memory = Int(Base.fld(__gcdiff.allocd, __evals))
579624
__allocs = Int(
@@ -585,7 +630,9 @@ function generate_benchmark_definition(
585630
__evals,
586631
),
587632
)
588-
return __time, __gctime, __memory, __allocs, __return_val
633+
return __time,
634+
__instructions, __branches, __gctime, __memory, __allocs,
635+
__return_val
589636
end
590637
$BenchmarkTools.Benchmark($(samplefunc), $(quote_vals), $(params))
591638
end,

src/groups.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Base.min(groups::BenchmarkGroup...) = mapvals(min, groups...)
113113
Base.max(groups::BenchmarkGroup...) = mapvals(max, groups...)
114114

115115
Base.time(group::BenchmarkGroup) = mapvals(time, group)
116+
instructions(group::BenchmarkGroup) = mapvals(instructions, group)
117+
branches(group::BenchmarkGroup) = mapvals(branches, group)
116118
gctime(group::BenchmarkGroup) = mapvals(gctime, group)
117119
memory(group::BenchmarkGroup) = mapvals(memory, group)
118120
allocs(group::BenchmarkGroup) = mapvals(allocs, group)

src/parameters.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ end
109109

110110
@noinline function overhead_sample(evals)
111111
start_time = time_ns()
112-
for _ in 1:evals
112+
try
113+
for _ in 1:evals
114+
nullfunc()
115+
end
116+
finally
113117
nullfunc()
114118
end
115119
sample_time = time_ns() - start_time

src/serialization.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ function recover(x::Vector)
5555
else
5656
xsi = if fn == "evals_set" && !haskey(fields, fn)
5757
false
58+
elseif fn in ("instructions", "branches")
59+
# JSON spec doesn't support NaN, so handle it specially here
60+
if !haskey(fields, fn)
61+
if ft === Vector{Float64}
62+
Float64[NaN for _ in length(fields["time"])]
63+
elseif ft === Float64
64+
NaN
65+
else
66+
@assert false
67+
end
68+
else
69+
if ft === Vector{Float64}
70+
Float64[
71+
elem === nothing ? NaN : convert(Float64, elem) for
72+
elem in fields[fn]
73+
]
74+
else
75+
fields[fn] === nothing ? NaN : convert(ft, fields[fn])
76+
end
77+
end
5878
elseif fn in ("seconds", "overhead", "time_tolerance", "memory_tolerance") &&
5979
fields[fn] === nothing
6080
# JSON spec doesn't support Inf

0 commit comments

Comments
 (0)