From a101c10414d432dc682b5c1c88ad997114b659c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Thu, 11 Dec 2025 15:32:14 +0100 Subject: [PATCH 1/7] Initial basic benchmarks --- benchmark/Project.toml | 5 ++++ benchmark/benchmarks.jl | 66 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 benchmark/Project.toml create mode 100644 benchmark/benchmarks.jl diff --git a/benchmark/Project.toml b/benchmark/Project.toml new file mode 100644 index 00000000..75cd78aa --- /dev/null +++ b/benchmark/Project.toml @@ -0,0 +1,5 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" +MPFI = "e50a78b8-6e2f-482e-b7c3-776aa2235d1a" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl new file mode 100644 index 00000000..301ed578 --- /dev/null +++ b/benchmark/benchmarks.jl @@ -0,0 +1,66 @@ +using BenchmarkTools +using IntervalArithmetic + +import MPFI +import Random + +MPFI.BigInterval(x::ExactReal) = MPFI.BigInterval(x.value) + +Random.seed!(0) + +# Dietmar Ratz functions +# NOTE We need to use @exact for bareinterval to be able to use the fast BareInterval constructor automatically +@exact dr1(x) = (x + sin(x)) * exp(-x^2) +@exact dr2(x) = x^4 - 10x^3 + 35x^2 - 50x + 24 +@exact dr3(x) = (log(x + 1.25) - 0.84x) ^ 2 +@exact dr4(x) = 0.02x^2 - 0.03exp(-(20(x - 0.875))^2) +@exact dr5(x) = exp(x^2) +@exact dr6(x) = x^4 - 12x^3 + 47x^2 - 60x - 20exp(-x) +@exact dr7(x) = x^6 - 15x^4 + 27x^2 + 250 +dr8(x) = atan(cos(tan(x))) +dr9(x) = asin(cos(acos(sin(x)))) + +dr_functions = [dr1, dr2, dr3, dr4, dr5, dr6, dr7, dr8, dr9] +dr_functions = [dr8, dr9] # MPFI doesn't support ^ + +basic_arithmetic = [+, *, -, /] +basic_functions = [exp, cosh, sinh, tanh, inv, sqrt, abs, log, sin, cos, tan, acos, asin, atan] + +interval_constructors = Dict( + "bareinterval" => bareinterval, + "interval" => interval, + "BigFloat bareinterval" => (x, y) -> bareinterval(BigFloat, x, y), + "BigFloat interval" => (x, y) -> interval(BigFloat, x, y), + "BigFloat MPFI" => MPFI.BigInterval +) + +bounds = map(1:100) do i + x = randn() + y = randn() + x > y && return (y, x) + return (x, y) +end + +SUITE = BenchmarkGroup() +SUITE["basics"] = BenchmarkGroup(["arithmetic"]) +SUITE["Dietmar-Ratz"] = BenchmarkGroup(["arithmetic"]) + +for (name, T) in interval_constructors + xx = [T(x, y) for (x, y) in bounds] + yy = reverse(xx) + + SUITE["basics"][name] = BenchmarkGroup(split(name)) + + for f in basic_functions + SUITE["basics"][name][string(f)] = @benchmarkable ($f).($xx) + end + + for op in basic_arithmetic + SUITE["basics"][name][string(op)] = @benchmarkable ($op).($xx, $yy) + end + + SUITE["Dietmar-Ratz"][name] = BenchmarkGroup(split(name)) + for dr in dr_functions + SUITE["Dietmar-Ratz"][name][string(dr)] = @benchmarkable ($dr).($xx) + end +end \ No newline at end of file From 8d54dafaa2abed647f4b2fc4144d2e105fc4e6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Mon, 22 Dec 2025 13:54:04 +0100 Subject: [PATCH 2/7] Clean up a bit and add analysis --- benchmark/Project.toml | 2 ++ benchmark/analyze.jl | 67 +++++++++++++++++++++++++++++++++++++++++ benchmark/benchmarks.jl | 17 ++++++----- 3 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 benchmark/analyze.jl diff --git a/benchmark/Project.toml b/benchmark/Project.toml index 75cd78aa..8f7afd85 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -1,5 +1,7 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" MPFI = "e50a78b8-6e2f-482e-b7c3-776aa2235d1a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/benchmark/analyze.jl b/benchmark/analyze.jl new file mode 100644 index 00000000..f07bfe22 --- /dev/null +++ b/benchmark/analyze.jl @@ -0,0 +1,67 @@ +using BenchmarkTools +using CairoMakie +using DataFrames + +include("benchmarks.jl") + +results = run(SUITE ; verbose = true) + +df = DataFrame(; constructor = String[], suite = String[], f = String[], trial = BenchmarkTools.Trial[]) + +for (name, T) in interval_constructors + for suite in suites + suite_df = DataFrame(results[name][suite], [:f, :trial]) + suite_df[:, :constructor] .= name + suite_df[:, :suite] .= suite + df = vcat(df, suite_df) + end +end + +transform!(df, + :trial => ByRow(trial -> minimum(trial.times)) => :minimum, + :trial => ByRow(trial -> mean(trial.times)) => :mean, + :trial => ByRow(trial -> median(trial.times)) => :median +) + +df[:, :relative] .= 0.0 + +for group in groupby(df, :f) + group[:, :relative] .= group[:, :minimum] ./ only(group[group.constructor .== "bareinterval", :minimum]) +end + +begin + fig = Figure(size = (800, 500)) + + data = df[df.suite .== "basics", :] + + fs = vcat(string.(basic_arithmetic), string.(basic_functions)) + to_x = Dict(f => k for (k, f) in enumerate(fs)) + xx = [to_x[f] for f in data.f] + + constructors = ["bareinterval", "interval", "BigFloat bareinterval", "BigFloat interval", "BigFloat MPFI"] + to_dodge = Dict(constructor => k for (k, constructor) in enumerate(constructors)) + dodge = [to_dodge[constructor] for constructor in data.constructor] + + ax = Axis(fig[1, 1] ; + xlabel = "Benchmarked function", + xticks = (1:length(fs), fs), + ylabel = "Relative execution time (log scale)", + yscale = log10, + ) + + barplot!(ax, xx, data.relative ; + dodge, + color = dodge, + colormap = :mpetroff_10, + colorrange = (1, 10), + ) + + Legend(fig[0, 1], + [LineElement(linewidth = 20, color = to_colormap(:mpetroff_10)[k]) for k in eachindex(constructors)], + constructors ; + tellwidth = false, + orientation = :horizontal + ) + + fig +end \ No newline at end of file diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 301ed578..84b64a47 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -5,6 +5,7 @@ import MPFI import Random MPFI.BigInterval(x::ExactReal) = MPFI.BigInterval(x.value) +Base.:(^)(x::MPFI.BigInterval, y::ExactReal) = x^y.value Random.seed!(0) @@ -21,7 +22,6 @@ dr8(x) = atan(cos(tan(x))) dr9(x) = asin(cos(acos(sin(x)))) dr_functions = [dr1, dr2, dr3, dr4, dr5, dr6, dr7, dr8, dr9] -dr_functions = [dr8, dr9] # MPFI doesn't support ^ basic_arithmetic = [+, *, -, /] basic_functions = [exp, cosh, sinh, tanh, inv, sqrt, abs, log, sin, cos, tan, acos, asin, atan] @@ -34,6 +34,8 @@ interval_constructors = Dict( "BigFloat MPFI" => MPFI.BigInterval ) +suites = ["basics", "Dietmar-Ratz"] + bounds = map(1:100) do i x = randn() y = randn() @@ -42,25 +44,24 @@ bounds = map(1:100) do i end SUITE = BenchmarkGroup() -SUITE["basics"] = BenchmarkGroup(["arithmetic"]) -SUITE["Dietmar-Ratz"] = BenchmarkGroup(["arithmetic"]) for (name, T) in interval_constructors xx = [T(x, y) for (x, y) in bounds] yy = reverse(xx) - SUITE["basics"][name] = BenchmarkGroup(split(name)) + SUITE[name] = BenchmarkGroup(split(name)) + SUITE[name]["basics"] = BenchmarkGroup(["arithmetic"]) + SUITE[name]["Dietmar-Ratz"] = BenchmarkGroup(["arithmetic"]) for f in basic_functions - SUITE["basics"][name][string(f)] = @benchmarkable ($f).($xx) + SUITE[name]["basics"][string(f)] = @benchmarkable ($f).($xx) end for op in basic_arithmetic - SUITE["basics"][name][string(op)] = @benchmarkable ($op).($xx, $yy) + SUITE[name]["basics"][string(op)] = @benchmarkable ($op).($xx, $yy) end - SUITE["Dietmar-Ratz"][name] = BenchmarkGroup(split(name)) for dr in dr_functions - SUITE["Dietmar-Ratz"][name][string(dr)] = @benchmarkable ($dr).($xx) + SUITE[name]["Dietmar-Ratz"][string(dr)] = @benchmarkable ($dr).($xx) end end \ No newline at end of file From 7bdbdf47ffb04bbd3657bb35f158666484af7633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Thu, 22 Jan 2026 12:07:20 +0100 Subject: [PATCH 3/7] WIP Make BigFloat interval faster --- benchmark/analyze.jl | 6 ++++-- benchmark/benchmarks.jl | 6 +++--- src/intervals/construction.jl | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/benchmark/analyze.jl b/benchmark/analyze.jl index f07bfe22..941b818b 100644 --- a/benchmark/analyze.jl +++ b/benchmark/analyze.jl @@ -9,7 +9,9 @@ results = run(SUITE ; verbose = true) df = DataFrame(; constructor = String[], suite = String[], f = String[], trial = BenchmarkTools.Trial[]) for (name, T) in interval_constructors - for suite in suites + # for suite in suites + begin + suite = "basics" suite_df = DataFrame(results[name][suite], [:f, :trial]) suite_df[:, :constructor] .= name suite_df[:, :suite] .= suite @@ -26,7 +28,7 @@ transform!(df, df[:, :relative] .= 0.0 for group in groupby(df, :f) - group[:, :relative] .= group[:, :minimum] ./ only(group[group.constructor .== "bareinterval", :minimum]) + group[:, :relative] .= group[:, :median] ./ only(group[group.constructor .== "bareinterval", :median]) end begin diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 84b64a47..29971778 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -61,7 +61,7 @@ for (name, T) in interval_constructors SUITE[name]["basics"][string(op)] = @benchmarkable ($op).($xx, $yy) end - for dr in dr_functions - SUITE[name]["Dietmar-Ratz"][string(dr)] = @benchmarkable ($dr).($xx) - end + # for dr in dr_functions + # SUITE[name]["Dietmar-Ratz"][string(dr)] = @benchmarkable ($dr).($xx) + # end end \ No newline at end of file diff --git a/src/intervals/construction.jl b/src/intervals/construction.jl index 4130da95..4f60d0f3 100644 --- a/src/intervals/construction.jl +++ b/src/intervals/construction.jl @@ -78,7 +78,8 @@ Internal constructor which assumes that `is_valid_interval(lo, hi) == true`. _unsafe_bareinterval(::Type{T}, a, b) where {T<:NumTypes} = _unsafe_bareinterval(T, _round(T, a, RoundDown), _round(T, b, RoundUp)) -_normalisezero(a) = ifelse(iszero(a), zero(a), a) +_normalisezero(a) = ifelse(iszero(a), zero(a), a) # Avoid branch in general +_normalisezero(a::BigFloat) = iszero(a) ? zero(a) : a # For BigFloat we avoid the allocation as much as possible # used only to construct intervals; needed to avoid `inf` and `sup` normalization _inf(x::BareInterval) = x.lo _sup(x::BareInterval) = x.hi From 69e7e95c24721c9be24539156224eb4cff93258d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Wed, 28 Jan 2026 18:41:48 +0100 Subject: [PATCH 4/7] Fix inf, sup, and abs --- src/intervals/arithmetic/absmax.jl | 4 +++- src/intervals/interval_operations/numeric.jl | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/intervals/arithmetic/absmax.jl b/src/intervals/arithmetic/absmax.jl index 3ccb6a7c..c660cf7e 100644 --- a/src/intervals/arithmetic/absmax.jl +++ b/src/intervals/arithmetic/absmax.jl @@ -10,7 +10,9 @@ Implement the `abs` function of the IEEE Standard 1788-2015 (Table 9.1). """ function Base.abs(x::BareInterval{T}) where {T<:NumTypes} isempty_interval(x) && return x - return _unsafe_bareinterval(T, mig(x), mag(x)) + inf(x) > 0 && return x + sup(x) < 0 && return _unsafe_bareinterval(T, -sup(x), -inf(x)) + return _unsafe_bareinterval(T, zero(T), max(-inf(x), sup(x))) end Base.abs(x::Interval) = _unsafe_interval(abs(bareinterval(x)), decoration(x), isguaranteed(x)) diff --git a/src/intervals/interval_operations/numeric.jl b/src/intervals/interval_operations/numeric.jl index 5fdd94fd..d1990aa9 100644 --- a/src/intervals/interval_operations/numeric.jl +++ b/src/intervals/interval_operations/numeric.jl @@ -14,7 +14,12 @@ Implement the `inf` function of the IEEE Standard 1788-2015 (Table 9.2). See also: [`sup`](@ref), [`bounds`](@ref), [`mid`](@ref), [`diam`](@ref), [`radius`](@ref) and [`midradius`](@ref). """ -inf(x::BareInterval{T}) where {T<:AbstractFloat} = ifelse(isnan(x.lo), typemax(T), ifelse(iszero(x.lo), copysign(x.lo, -1), x.lo)) +function inf(x::BareInterval{T}) where {T<:AbstractFloat} + isnan(x.lo) && return typemax(T) + iszero(x.lo) && return copysign(x.lo, -1) + return x.lo +end + inf(x::BareInterval{<:Rational}) = x.lo 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). See also: [`inf`](@ref), [`bounds`](@ref), [`mid`](@ref), [`diam`](@ref), [`radius`](@ref) and [`midradius`](@ref). """ -sup(x::BareInterval{T}) where {T<:AbstractFloat} = ifelse(isnan(x.hi), typemin(T), x.hi) +function sup(x::BareInterval{T}) where {T<:AbstractFloat} + isnan(x.hi) && return typemin(T) + return x.hi +end + sup(x::BareInterval{<:Rational}) = x.hi function sup(x::Interval{T}) where {T<:AbstractFloat} From 181683741eceb55b8b65e131998d784739fa2a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Fri, 13 Feb 2026 17:18:05 +0100 Subject: [PATCH 5/7] Optimize sqrt for BigFloat --- benchmark/Project.toml | 1 - src/intervals/arithmetic/basic.jl | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/benchmark/Project.toml b/benchmark/Project.toml index 8f7afd85..5574c88c 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -1,6 +1,5 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" MPFI = "e50a78b8-6e2f-482e-b7c3-776aa2235d1a" diff --git a/src/intervals/arithmetic/basic.jl b/src/intervals/arithmetic/basic.jl index ed85d633..3068a88c 100644 --- a/src/intervals/arithmetic/basic.jl +++ b/src/intervals/arithmetic/basic.jl @@ -238,10 +238,9 @@ end Implement the `sqrt` function of the IEEE Standard 1788-2015 (Table 9.1). """ function Base.sqrt(x::BareInterval{T}) where {T<:AbstractFloat} - domain = _unsafe_bareinterval(T, zero(T), typemax(T)) - x = intersect_interval(x, domain) - isempty_interval(x) && return x - return @round(T, sqrt(inf(x)), sqrt(sup(x))) + sup(x) < 0 && return emptyinterval(BareInterval{T}) + lo = inf(x) < 0 ? zero(T) : inf(x) + return @round(T, sqrt(lo), sqrt(sup(x))) end Base.sqrt(x::BareInterval{<:Rational}) = sqrt(float(x)) From 8518bfe118628e1d71368ae6265f7a5d38819c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Tue, 3 Mar 2026 10:08:19 +0100 Subject: [PATCH 6/7] Add the benchmark GitHub action from AirspeedVelocity.jl --- .github/workflows/benchmark.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..e7b55343 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,14 @@ +name: Benchmark this PR +on: + pull_request_target: + branches: [ master ] # change to your default branch +permissions: + pull-requests: write # needed to post comments + +jobs: + bench: + runs-on: ubuntu-latest + steps: + - uses: MilesCranmer/AirspeedVelocity.jl@action-v1 + with: + julia-version: '1' \ No newline at end of file From fef1c88106a8c16796228b6c8d66256c51c8b8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Tue, 3 Mar 2026 11:52:20 +0100 Subject: [PATCH 7/7] Clean up benchmarks --- benchmark/Project.toml | 4 +++- benchmark/benchmarks.jl | 24 +++--------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/benchmark/Project.toml b/benchmark/Project.toml index 5574c88c..396d4150 100644 --- a/benchmark/Project.toml +++ b/benchmark/Project.toml @@ -1,6 +1,8 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" MPFI = "e50a78b8-6e2f-482e-b7c3-776aa2235d1a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[sources] +MPFI = {url = "https://gitlab.inria.fr/ckatsama/mpfi.jl.git"} \ No newline at end of file diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index 29971778..51eace47 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -9,20 +9,6 @@ Base.:(^)(x::MPFI.BigInterval, y::ExactReal) = x^y.value Random.seed!(0) -# Dietmar Ratz functions -# NOTE We need to use @exact for bareinterval to be able to use the fast BareInterval constructor automatically -@exact dr1(x) = (x + sin(x)) * exp(-x^2) -@exact dr2(x) = x^4 - 10x^3 + 35x^2 - 50x + 24 -@exact dr3(x) = (log(x + 1.25) - 0.84x) ^ 2 -@exact dr4(x) = 0.02x^2 - 0.03exp(-(20(x - 0.875))^2) -@exact dr5(x) = exp(x^2) -@exact dr6(x) = x^4 - 12x^3 + 47x^2 - 60x - 20exp(-x) -@exact dr7(x) = x^6 - 15x^4 + 27x^2 + 250 -dr8(x) = atan(cos(tan(x))) -dr9(x) = asin(cos(acos(sin(x)))) - -dr_functions = [dr1, dr2, dr3, dr4, dr5, dr6, dr7, dr8, dr9] - basic_arithmetic = [+, *, -, /] basic_functions = [exp, cosh, sinh, tanh, inv, sqrt, abs, log, sin, cos, tan, acos, asin, atan] @@ -36,9 +22,9 @@ interval_constructors = Dict( suites = ["basics", "Dietmar-Ratz"] -bounds = map(1:100) do i - x = randn() - y = randn() +bounds = map(range(0.01, 10 ; length = 100)) do i + x = i * randn() + y = i * randn() x > y && return (y, x) return (x, y) end @@ -60,8 +46,4 @@ for (name, T) in interval_constructors for op in basic_arithmetic SUITE[name]["basics"][string(op)] = @benchmarkable ($op).($xx, $yy) end - - # for dr in dr_functions - # SUITE[name]["Dietmar-Ratz"][string(dr)] = @benchmarkable ($dr).($xx) - # end end \ No newline at end of file