Skip to content

Commit 471a226

Browse files
authored
Add benchmarks (#738)
* Initial basic benchmarks * Clean up a bit and add analysis * WIP Make BigFloat interval faster * Fix inf, sup, and abs * Optimize sqrt for BigFloat * Add the benchmark GitHub action from AirspeedVelocity.jl * Clean up benchmarks
1 parent b3f2756 commit 471a226

File tree

8 files changed

+159
-8
lines changed

8 files changed

+159
-8
lines changed

.github/workflows/benchmark.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Benchmark this PR
2+
on:
3+
pull_request_target:
4+
branches: [ master ] # change to your default branch
5+
permissions:
6+
pull-requests: write # needed to post comments
7+
8+
jobs:
9+
bench:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: MilesCranmer/AirspeedVelocity.jl@action-v1
13+
with:
14+
julia-version: '1'

benchmark/Project.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[deps]
2+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3+
IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
4+
MPFI = "e50a78b8-6e2f-482e-b7c3-776aa2235d1a"
5+
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
6+
7+
[sources]
8+
MPFI = {url = "https://gitlab.inria.fr/ckatsama/mpfi.jl.git"}

benchmark/analyze.jl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using BenchmarkTools
2+
using CairoMakie
3+
using DataFrames
4+
5+
include("benchmarks.jl")
6+
7+
results = run(SUITE ; verbose = true)
8+
9+
df = DataFrame(; constructor = String[], suite = String[], f = String[], trial = BenchmarkTools.Trial[])
10+
11+
for (name, T) in interval_constructors
12+
# for suite in suites
13+
begin
14+
suite = "basics"
15+
suite_df = DataFrame(results[name][suite], [:f, :trial])
16+
suite_df[:, :constructor] .= name
17+
suite_df[:, :suite] .= suite
18+
df = vcat(df, suite_df)
19+
end
20+
end
21+
22+
transform!(df,
23+
:trial => ByRow(trial -> minimum(trial.times)) => :minimum,
24+
:trial => ByRow(trial -> mean(trial.times)) => :mean,
25+
:trial => ByRow(trial -> median(trial.times)) => :median
26+
)
27+
28+
df[:, :relative] .= 0.0
29+
30+
for group in groupby(df, :f)
31+
group[:, :relative] .= group[:, :median] ./ only(group[group.constructor .== "bareinterval", :median])
32+
end
33+
34+
begin
35+
fig = Figure(size = (800, 500))
36+
37+
data = df[df.suite .== "basics", :]
38+
39+
fs = vcat(string.(basic_arithmetic), string.(basic_functions))
40+
to_x = Dict(f => k for (k, f) in enumerate(fs))
41+
xx = [to_x[f] for f in data.f]
42+
43+
constructors = ["bareinterval", "interval", "BigFloat bareinterval", "BigFloat interval", "BigFloat MPFI"]
44+
to_dodge = Dict(constructor => k for (k, constructor) in enumerate(constructors))
45+
dodge = [to_dodge[constructor] for constructor in data.constructor]
46+
47+
ax = Axis(fig[1, 1] ;
48+
xlabel = "Benchmarked function",
49+
xticks = (1:length(fs), fs),
50+
ylabel = "Relative execution time (log scale)",
51+
yscale = log10,
52+
)
53+
54+
barplot!(ax, xx, data.relative ;
55+
dodge,
56+
color = dodge,
57+
colormap = :mpetroff_10,
58+
colorrange = (1, 10),
59+
)
60+
61+
Legend(fig[0, 1],
62+
[LineElement(linewidth = 20, color = to_colormap(:mpetroff_10)[k]) for k in eachindex(constructors)],
63+
constructors ;
64+
tellwidth = false,
65+
orientation = :horizontal
66+
)
67+
68+
fig
69+
end

benchmark/benchmarks.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using BenchmarkTools
2+
using IntervalArithmetic
3+
4+
import MPFI
5+
import Random
6+
7+
MPFI.BigInterval(x::ExactReal) = MPFI.BigInterval(x.value)
8+
Base.:(^)(x::MPFI.BigInterval, y::ExactReal) = x^y.value
9+
10+
Random.seed!(0)
11+
12+
basic_arithmetic = [+, *, -, /]
13+
basic_functions = [exp, cosh, sinh, tanh, inv, sqrt, abs, log, sin, cos, tan, acos, asin, atan]
14+
15+
interval_constructors = Dict(
16+
"bareinterval" => bareinterval,
17+
"interval" => interval,
18+
"BigFloat bareinterval" => (x, y) -> bareinterval(BigFloat, x, y),
19+
"BigFloat interval" => (x, y) -> interval(BigFloat, x, y),
20+
"BigFloat MPFI" => MPFI.BigInterval
21+
)
22+
23+
suites = ["basics", "Dietmar-Ratz"]
24+
25+
bounds = map(range(0.01, 10 ; length = 100)) do i
26+
x = i * randn()
27+
y = i * randn()
28+
x > y && return (y, x)
29+
return (x, y)
30+
end
31+
32+
SUITE = BenchmarkGroup()
33+
34+
for (name, T) in interval_constructors
35+
xx = [T(x, y) for (x, y) in bounds]
36+
yy = reverse(xx)
37+
38+
SUITE[name] = BenchmarkGroup(split(name))
39+
SUITE[name]["basics"] = BenchmarkGroup(["arithmetic"])
40+
SUITE[name]["Dietmar-Ratz"] = BenchmarkGroup(["arithmetic"])
41+
42+
for f in basic_functions
43+
SUITE[name]["basics"][string(f)] = @benchmarkable ($f).($xx)
44+
end
45+
46+
for op in basic_arithmetic
47+
SUITE[name]["basics"][string(op)] = @benchmarkable ($op).($xx, $yy)
48+
end
49+
end

src/intervals/arithmetic/absmax.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ Implement the `abs` function of the IEEE Standard 1788-2015 (Table 9.1).
1010
"""
1111
function Base.abs(x::BareInterval{T}) where {T<:NumTypes}
1212
isempty_interval(x) && return x
13-
return _unsafe_bareinterval(T, mig(x), mag(x))
13+
inf(x) > 0 && return x
14+
sup(x) < 0 && return _unsafe_bareinterval(T, -sup(x), -inf(x))
15+
return _unsafe_bareinterval(T, zero(T), max(-inf(x), sup(x)))
1416
end
1517

1618
Base.abs(x::Interval) = _unsafe_interval(abs(bareinterval(x)), decoration(x), isguaranteed(x))

src/intervals/arithmetic/basic.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,9 @@ end
238238
Implement the `sqrt` function of the IEEE Standard 1788-2015 (Table 9.1).
239239
"""
240240
function Base.sqrt(x::BareInterval{T}) where {T<:AbstractFloat}
241-
domain = _unsafe_bareinterval(T, zero(T), typemax(T))
242-
x = intersect_interval(x, domain)
243-
isempty_interval(x) && return x
244-
return @round(T, sqrt(inf(x)), sqrt(sup(x)))
241+
sup(x) < 0 && return emptyinterval(BareInterval{T})
242+
lo = inf(x) < 0 ? zero(T) : inf(x)
243+
return @round(T, sqrt(lo), sqrt(sup(x)))
245244
end
246245

247246
Base.sqrt(x::BareInterval{<:Rational}) = sqrt(float(x))

src/intervals/construction.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ Internal constructor which assumes that `is_valid_interval(lo, hi) == true`.
7878
_unsafe_bareinterval(::Type{T}, a, b) where {T<:NumTypes} =
7979
_unsafe_bareinterval(T, _round(T, a, RoundDown), _round(T, b, RoundUp))
8080

81-
_normalisezero(a) = ifelse(iszero(a), zero(a), a)
81+
_normalisezero(a) = ifelse(iszero(a), zero(a), a) # Avoid branch in general
82+
_normalisezero(a::BigFloat) = iszero(a) ? zero(a) : a # For BigFloat we avoid the allocation as much as possible
8283
# used only to construct intervals; needed to avoid `inf` and `sup` normalization
8384
_inf(x::BareInterval) = x.lo
8485
_sup(x::BareInterval) = x.hi

src/intervals/interval_operations/numeric.jl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ Implement the `inf` function of the IEEE Standard 1788-2015 (Table 9.2).
1414
See also: [`sup`](@ref), [`bounds`](@ref), [`mid`](@ref), [`diam`](@ref),
1515
[`radius`](@ref) and [`midradius`](@ref).
1616
"""
17-
inf(x::BareInterval{T}) where {T<:AbstractFloat} = ifelse(isnan(x.lo), typemax(T), ifelse(iszero(x.lo), copysign(x.lo, -1), x.lo))
17+
function inf(x::BareInterval{T}) where {T<:AbstractFloat}
18+
isnan(x.lo) && return typemax(T)
19+
iszero(x.lo) && return copysign(x.lo, -1)
20+
return x.lo
21+
end
22+
1823
inf(x::BareInterval{<:Rational}) = x.lo
1924

2025
function inf(x::Interval{T}) where {T<:AbstractFloat}
@@ -38,7 +43,11 @@ Implement the `sup` function of the IEEE Standard 1788-2015 (Table 9.2).
3843
See also: [`inf`](@ref), [`bounds`](@ref), [`mid`](@ref), [`diam`](@ref),
3944
[`radius`](@ref) and [`midradius`](@ref).
4045
"""
41-
sup(x::BareInterval{T}) where {T<:AbstractFloat} = ifelse(isnan(x.hi), typemin(T), x.hi)
46+
function sup(x::BareInterval{T}) where {T<:AbstractFloat}
47+
isnan(x.hi) && return typemin(T)
48+
return x.hi
49+
end
50+
4251
sup(x::BareInterval{<:Rational}) = x.hi
4352

4453
function sup(x::Interval{T}) where {T<:AbstractFloat}

0 commit comments

Comments
 (0)