Skip to content

Commit c1ea8bb

Browse files
authored
0-allocation mutable rem (#235)
* 0-allocation mutable rem * Rename constant operations * Checkout DP#bl/mutable_rem * Fix constant_function -> right_constant_function * Add Benchmark 0 * Mutable one in primitive_univariate_gcd * 0-alloc for BigInt as well * Format * Tests fixes * Fix * Rename type arg * Add independence tests * Fix missing copies * Benchmark more types * Exclude test on Julia v1.6 * Missing blank line * Factor out divided check * Add test * Add rem_or_pseudo_rem * Export * Fix * Fix * Fix * Mutable rem for Rational{BigInt} * Fix * Fix test * Add TODO
1 parent 3a67e3a commit c1ea8bb

17 files changed

+500
-149
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
shell: julia --project=@. {0}
5252
run: |
5353
using Pkg
54-
Pkg.add(PackageSpec(name="DynamicPolynomials", rev="master"))
54+
Pkg.add(PackageSpec(name="DynamicPolynomials", rev="bl/mutable_rem"))
5555
- uses: julia-actions/julia-buildpkg@v1
5656
- uses: julia-actions/julia-runtest@v1
5757
with:

docs/src/division.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ The `divrem` function returns ``(q, r)``.
1212

1313
```@docs
1414
divides
15+
pseudo_rem
16+
rem_or_pseudo_rem
1517
```

perf/Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[deps]
2+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3+
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
4+
SIMDPolynomials = "4edc584b-a88b-4acf-80bb-891198a11e01"
5+
TypedPolynomials = "afbbf031-7a57-5f58-a1b9-b774a0fad08d"

perf/gcd.jl

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
using BenchmarkTools
22

3-
function bench0(x, T=Rational{BigInt})
3+
function bench0(x, T)
44
o = one(T)
55
p = o * x^2 + 2o * x + o
66
q = o * x + o
77
@benchmark gcd($p, $q)
88
end
99

10-
function bench1(x, y, z, T=Rational{BigInt})
10+
function bench1(x, y, z, T)
1111
o = one(T)
1212
p = (o * x + o * y^2) * (o * z^3 + o * y^2 + o * x)
1313
q = (o * x + o * y + o * z) * (o * x^2 + o * y)
1414
@benchmark gcd($p, $q)
1515
end
1616

17-
function bench2(x, y, z, t)
18-
p = x * y + 3 * (z * t)
19-
q = (p + 1) * p
17+
function bench2(x, y, z, t, T)
18+
p = x * y + T(3) * (z * t)
19+
q = (p + T(1)) * p
2020
@benchmark gcd($p, $q)
2121
end
2222

23-
function bench3(x, y, z, t)
24-
c1 = 10*(x * z + x)
25-
c2 = 2*(x^2 + z)
26-
c3 = 2*(2 - z )
27-
c4 = 20*(x * z^2)
23+
function bench3(x, y, z, t, T)
24+
c1 = T(10)*(x * z + x)
25+
c2 = T(2)*(x^2 + z)
26+
c3 = T(2)*(2 - z)
27+
c4 = T(20)*(x * z^2)
2828
e1 = 0
2929
e2 = 5
3030
e3 = 7
@@ -37,39 +37,63 @@ function bench3(x, y, z, t)
3737
end
3838

3939
import SIMDPolynomials
40-
function bench_SP()
40+
function bench_SP(T)
4141
x, y, z, t = [SIMDPolynomials.PackedMonomial{4,7}(i) for i in 0:3]
42-
b1 = bench1(x, y, z)
43-
b2 = bench2(x, y, z, t)
44-
b3 = bench3(x, y, z, t)
45-
return b1, b2, b3
42+
b0 = bench0(x, T)
43+
b1 = bench1(x, y, z, T)
44+
b2 = bench2(x, y, z, t, T)
45+
b3 = bench3(x, y, z, t, T)
46+
return b0, b1, b2, b3
4647
end
4748

4849
import DynamicPolynomials
49-
function bench_DP()
50+
function bench_DP(T)
5051
DynamicPolynomials.@polyvar x y z t
51-
b1 = bench1(x, y, z)
52-
b2 = bench2(x, y, z, t)
53-
b3 = bench3(x, y, z, t)
54-
return b1, b2, b3
52+
b0 = bench0(x, T)
53+
b1 = bench1(x, y, z, T)
54+
b2 = bench2(x, y, z, t, T)
55+
b3 = bench3(x, y, z, t, T)
56+
return b0, b1, b2, b3
5557
end
5658

5759
import TypedPolynomials
58-
function bench_TP()
60+
function bench_TP(T)
5961
TypedPolynomials.@polyvar x y z t
60-
b1 = bench1(x, y, z)
61-
b2 = bench2(x, y, z, t)
62-
b3 = bench3(x, y, z, t)
63-
return b1, b2, b3
62+
b0 = bench0(x, T)
63+
b1 = bench1(x, y, z, T)
64+
b2 = bench2(x, y, z, t, T)
65+
b3 = bench3(x, y, z, t, T)
66+
return b0, b1, b2, b3
6467
end
6568

6669
include("table.jl")
6770

68-
function bench()
69-
bs1, bs2, bs3 = bench_SP()
70-
bd1, bd2, bd3 = bench_DP()
71-
bt1, bt2, bt3 = bench_TP()
71+
function bench(T)
72+
bs0, bs1, bs2, bs3 = bench_SP(T)
73+
bd0, bd1, bd2, bd3 = bench_DP(T)
74+
bt0, bt1, bt2, bt3 = bench_TP(T)
75+
println("### Benchmark 0")
76+
println()
77+
prettyprint(bs0, bd0, bt0)
78+
println()
79+
println("### Benchmark 1")
80+
println()
7281
prettyprint(bs1, bd1, bt1)
82+
println()
83+
println("### Benchmark 2")
84+
println()
7385
prettyprint(bs2, bd2, bt2)
86+
println()
87+
println("### Benchmark 3")
88+
println()
7489
prettyprint(bs3, bd3, bt3)
90+
println()
91+
end
92+
93+
function bench()
94+
for T in [Int, BigInt, Rational{BigInt}]
95+
println("## $T")
96+
println()
97+
bench(T)
98+
end
7599
end

src/comparison.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Base.iszero(t::AbstractTerm) = iszero(coefficient(t))
66
Base.iszero(t::AbstractPolynomial) = iszero(nterms(t))
77

88
# See https://github.com/blegat/MultivariatePolynomials.jl/issues/22
9-
# avoids the call to be transfered to eqconstant
9+
# avoids the call to be transfered to left_constant_eq
1010
Base.:(==)(α::Nothing, x::APL) = false
1111
Base.:(==)(x::APL, α::Nothing) = false
1212
Base.:(==)(α::Dict, x::APL) = false
@@ -26,19 +26,19 @@ function polyeqterm(p::AbstractPolynomial, t)
2626
end
2727
polyeqterm(p::APL, t) = polyeqterm(polynomial(p), t)
2828

29-
eqconstant(α, v::AbstractVariable) = false
30-
eqconstant(v::AbstractVariable, α) = false
31-
function _termeqconstant(t::AbstractTermLike, α)
29+
left_constant_eq(α, v::AbstractVariable) = false
30+
right_constant_eq(v::AbstractVariable, α) = false
31+
function _term_constant_eq(t::AbstractTermLike, α)
3232
if iszero(t)
3333
iszero(α)
3434
else
3535
α == coefficient(t) && isconstant(t)
3636
end
3737
end
38-
eqconstant(α, t::AbstractTermLike) = _termeqconstant(t, α)
39-
eqconstant(t::AbstractTermLike, α) = _termeqconstant(t, α)
40-
eqconstant(α, p::APL) = polyeqterm(p, α)
41-
eqconstant(p::APL, α) = polyeqterm(p, α)
38+
left_constant_eq(α, t::AbstractTermLike) = _term_constant_eq(t, α)
39+
right_constant_eq(t::AbstractTermLike, α) = _term_constant_eq(t, α)
40+
left_constant_eq(α, p::APL) = polyeqterm(p, α)
41+
right_constant_eq(p::APL, α) = polyeqterm(p, α)
4242

4343
function Base.:(==)(mono::AbstractMonomial, v::AbstractVariable)
4444
return isone(degree(mono)) && variable(mono) == v

src/default_polynomial.jl

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -150,32 +150,85 @@ end
150150
Base.copy(p::VectorPolynomial) = MA.mutable_copy(p)
151151

152152
function grlex end
153-
function MA.operate!(op::Union{typeof(+), typeof(-)}, p::Polynomial{T,TT}, q::Union{Polynomial,AbstractTermLike}) where {T,TT}
154-
get1 = let p=p
155-
i -> p.terms[i]
153+
154+
function __polynomial_merge!(op::MA.AddSubMul, p::Polynomial{T,TT}, get1, set, push, resize, keep, q, t::AbstractTermLike...) where {T,TT}
155+
compare_monomials = let t=t, get1=get1, q=q
156+
(tp, j) -> begin
157+
if tp isa Int && j isa Int
158+
tp = get1(tp)
159+
end
160+
grlex(*(monomials(q)[j], monomial.(t)...), monomial(tp))
161+
end
156162
end
157-
get2 = let p=p
163+
get2 = let t=t, q=q
158164
i -> begin
159-
t = terms(q)[i]
160-
TT(MA.scaling_convert(T, MA.operate(op, coefficient(t))), monomial(t))
165+
tq = terms(q)[i]
166+
TT(MA.scaling_convert(T, MA.operate(MA.add_sub_op(op), *(coefficient(tq), coefficient.(t)...))), *(monomial(tq), monomial.(t)...))
161167
end
162168
end
163-
set = let p=p
164-
(i, t) -> begin
165-
p.terms[i] = t
169+
combine = let t=t, p=p, q=q
170+
(i, j) -> begin
171+
if i isa Int
172+
p.terms[i] = Term(MA.operate!!(op, coefficient(p.terms[i]), coefficients(q)[j], coefficient.(t)...), monomial(p.terms[i]))
173+
else
174+
typeof(i)(MA.operate!!(op, coefficient(i), coefficients(q)[j], coefficient.(t)...), monomial(i))
175+
end
166176
end
167177
end
168-
push = let p=p
169-
t -> push!(p.terms, t)
178+
polynomial_merge!(
179+
nterms(p), nterms(q), get1, get2, set, push,
180+
compare_monomials, combine, keep, resize
181+
)
182+
return
183+
end
184+
185+
function __polynomial_merge!(op::MA.AddSubMul, p::Polynomial{T,TT}, get1, set, push, resize, keep, t::AbstractTermLike, q::Polynomial, buffer=nothing) where {T,TT}
186+
compare_monomials = let t=t, get1=get1, q=q
187+
(tp, j) -> begin
188+
if tp isa Int && j isa Int
189+
tp = get1(tp)
190+
end
191+
grlex(monomial(t) * monomials(q)[j], monomial(tp))
192+
end
193+
end
194+
get2 = let t=t, q=q
195+
i -> begin
196+
tq = terms(q)[i]
197+
TT(MA.scaling_convert(T, MA.operate(MA.add_sub_op(op), coefficient(t) * coefficient(tq))), monomial(t) * monomial(tq))
198+
end
199+
end
200+
combine = let t=t, p=p, q=q
201+
(i, j) -> begin
202+
if i isa Int
203+
p.terms[i] = Term(MA.buffered_operate!!(buffer, op, coefficient(p.terms[i]), coefficient(t), coefficients(q)[j]), monomial(p.terms[i]))
204+
else
205+
typeof(i)(MA.buffered_operate!!(buffer, op, coefficient(i), coefficient(t), coefficients(q)[j]), monomial(i))
206+
end
207+
end
170208
end
171-
compare_monomials = let q=q
209+
polynomial_merge!(
210+
nterms(p), nterms(q), get1, get2, set, push,
211+
compare_monomials, combine, keep, resize
212+
)
213+
return
214+
end
215+
216+
function __polynomial_merge!(op::Union{typeof(+), typeof(-)}, p::Polynomial{T,TT}, get1, set, push, resize, keep, q::Union{Polynomial,AbstractTermLike}) where {T,TT}
217+
compare_monomials = let get1=get1, q=q
172218
(t, j) -> begin
173219
if t isa Int && j isa Int
174220
t = get1(t)
175221
end
176222
grlex(monomials(q)[j], monomial(t))
177223
end
178224
end
225+
get2 = let q=q
226+
i -> begin
227+
t = terms(q)[i]
228+
# `operate` makes sure we make a copy of the term as it may be stored directly in `p`
229+
TT(MA.scaling_convert(T, MA.operate(op, coefficient(t))), monomial(t))
230+
end
231+
end
179232
combine = let p=p, q=q
180233
(i, j) -> begin
181234
if i isa Int
@@ -185,6 +238,25 @@ function MA.operate!(op::Union{typeof(+), typeof(-)}, p::Polynomial{T,TT}, q::Un
185238
end
186239
end
187240
end
241+
polynomial_merge!(
242+
nterms(p), nterms(q), get1, get2, set, push,
243+
compare_monomials, combine, keep, resize
244+
)
245+
return
246+
end
247+
248+
function _polynomial_merge!(op::Union{typeof(+), typeof(-), MA.AddSubMul}, p::Polynomial{T,TT}, args...) where {T,TT}
249+
get1 = let p=p
250+
i -> p.terms[i]
251+
end
252+
set = let p=p
253+
(i, t) -> begin
254+
p.terms[i] = t
255+
end
256+
end
257+
push = let p=p
258+
t -> push!(p.terms, t)
259+
end
188260
resize = let p=p
189261
(n) -> resize!(p.terms, n)
190262
end
@@ -198,12 +270,29 @@ function MA.operate!(op::Union{typeof(+), typeof(-)}, p::Polynomial{T,TT}, q::Un
198270
end
199271
end
200272
end
201-
polynomial_merge!(
202-
nterms(p), nterms(q), get1, get2, set, push,
203-
compare_monomials, combine, keep, resize
204-
)
273+
__polynomial_merge!(op, p, get1, set, push, resize, keep, args...)
205274
return p
206275
end
276+
277+
function MA.operate!(op::Union{typeof(+), typeof(-)}, p::Polynomial, q::Union{AbstractTermLike,Polynomial})
278+
return _polynomial_merge!(op, p, q)
279+
end
280+
281+
function MA.operate!(op::MA.AddSubMul, p::Polynomial, q::Polynomial, args::AbstractTermLike...)
282+
return _polynomial_merge!(op, p, q, args...)
283+
end
284+
285+
function MA.operate!(op::MA.AddSubMul, p::Polynomial, t::AbstractTermLike, q::Polynomial)
286+
return _polynomial_merge!(op, p, t, q)
287+
end
288+
289+
function MA.buffer_for(op::MA.AddSubMul, ::Type{<:Polynomial{S}}, ::Type{<:AbstractTermLike{T}}, ::Type{<:Polynomial{U}}) where {S,T,U}
290+
return MA.buffer_for(op, S, T, U)
291+
end
292+
function MA.buffered_operate!(buffer, op::MA.AddSubMul, p::Polynomial, t::AbstractTermLike, q::Polynomial)
293+
return _polynomial_merge!(op, p, t, q, buffer)
294+
end
295+
207296
function MA.operate_to!(output::Polynomial, ::typeof(*), p::Polynomial, q::Polynomial)
208297
empty!(output.terms)
209298
mul_to_terms!(output.terms, p, q)
@@ -214,6 +303,12 @@ end
214303
function MA.operate!(::typeof(*), p::Polynomial, q::Polynomial)
215304
return MA.operate_to!(p, *, MA.mutable_copy(p), q)
216305
end
306+
function MA.operate!(::typeof(*), p::Polynomial, t::AbstractTermLike)
307+
for i in eachindex(p.terms)
308+
p.terms[i] = MA.operate!!(*, p.terms[i], t)
309+
end
310+
return p
311+
end
217312

218313
function MA.operate!(::typeof(zero), p::Polynomial)
219314
empty!(p.terms)
@@ -234,3 +329,9 @@ function MA.operate!(::typeof(removeleadingterm), p::Polynomial)
234329
pop!(p.terms)
235330
return p
236331
end
332+
333+
function MA.operate!(::typeof(unsafe_restore_leading_term), p::Polynomial, t::AbstractTermLike)
334+
# We don't need to copy the coefficient of `t`, this is why this function is called `unsafe`
335+
push!(p.terms, t)
336+
return p
337+
end

src/default_term.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ function MA.promote_operation(::typeof(combine), ::Type{Term{S, M1}}, ::Type{Ter
3131
return Term{MA.promote_operation(+, S, T), promote_type(M1, M2)}
3232
end
3333

34-
function MA.mutability(::Type{Term{C,T}}) where {C,T}
35-
if MA.mutability(C) isa MA.IsMutable && MA.mutability(T) isa MA.IsMutable
34+
function MA.mutability(::Type{Term{C,M}}) where {C,M}
35+
if MA.mutability(C) isa MA.IsMutable && MA.mutability(M) isa MA.IsMutable
3636
return MA.IsMutable()
3737
else
3838
return MA.IsNotMutable()

0 commit comments

Comments
 (0)