Skip to content

Commit 75393f6

Browse files
authoredOct 1, 2024··
make faster BigFloats (#55906)
We can coalesce the two required allocations for the MFPR BigFloat API design into one allocation, hopefully giving a easy performance boost. It would have been slightly easier and more efficient if MPFR BigFloat was already a VLA instead of containing a pointer here, but that does not prevent the optimization.
1 parent 61802e2 commit 75393f6

File tree

6 files changed

+138
-102
lines changed

6 files changed

+138
-102
lines changed
 

‎base/Base.jl

-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ end
306306
include("hashing.jl")
307307
include("rounding.jl")
308308
include("div.jl")
309-
include("rawbigints.jl")
310309
include("float.jl")
311310
include("twiceprecision.jl")
312311
include("complex.jl")

‎base/mpfr.jl

+109-52
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ import
1818
setrounding, maxintfloat, widen, significand, frexp, tryparse, iszero,
1919
isone, big, _string_n, decompose, minmax, _precision_with_base_2,
2020
sinpi, cospi, sincospi, tanpi, sind, cosd, tand, asind, acosd, atand,
21-
uinttype, exponent_max, exponent_min, ieee754_representation, significand_mask,
22-
RawBigIntRoundingIncrementHelper, truncated, RawBigInt
23-
21+
uinttype, exponent_max, exponent_min, ieee754_representation, significand_mask
2422

2523
using .Base.Libc
26-
import ..Rounding:
24+
import ..Rounding: Rounding,
2725
rounding_raw, setrounding_raw, rounds_to_nearest, rounds_away_from_zero,
2826
tie_breaker_is_to_even, correct_rounding_requires_increment
2927

@@ -39,7 +37,6 @@ else
3937
const libmpfr = "libmpfr.so.6"
4038
end
4139

42-
4340
version() = VersionNumber(unsafe_string(ccall((:mpfr_get_version,libmpfr), Ptr{Cchar}, ())))
4441
patches() = split(unsafe_string(ccall((:mpfr_get_patches,libmpfr), Ptr{Cchar}, ())),' ')
4542

@@ -120,69 +117,129 @@ const mpfr_special_exponent_zero = typemin(Clong) + true
120117
const mpfr_special_exponent_nan = mpfr_special_exponent_zero + true
121118
const mpfr_special_exponent_inf = mpfr_special_exponent_nan + true
122119

120+
struct BigFloatLayout
121+
prec::Clong
122+
sign::Cint
123+
exp::Clong
124+
d::Ptr{Limb}
125+
# possible padding
126+
p::Limb # Tuple{Vararg{Limb}}
127+
end
128+
const offset_prec = fieldoffset(BigFloatLayout, 1) % Int
129+
const offset_sign = fieldoffset(BigFloatLayout, 2) % Int
130+
const offset_exp = fieldoffset(BigFloatLayout, 3) % Int
131+
const offset_d = fieldoffset(BigFloatLayout, 4) % Int
132+
const offset_p_limbs = ((fieldoffset(BigFloatLayout, 5) % Int + sizeof(Limb) - 1) ÷ sizeof(Limb))
133+
const offset_p = offset_p_limbs * sizeof(Limb)
134+
123135
"""
124136
BigFloat <: AbstractFloat
125137
126138
Arbitrary precision floating point number type.
127139
"""
128-
mutable struct BigFloat <: AbstractFloat
129-
prec::Clong
130-
sign::Cint
131-
exp::Clong
132-
d::Ptr{Limb}
133-
# _d::Buffer{Limb} # Julia gc handle for memory @ d
134-
_d::String # Julia gc handle for memory @ d (optimized)
140+
struct BigFloat <: AbstractFloat
141+
d::Memory{Limb}
135142

136143
# Not recommended for general use:
137144
# used internally by, e.g. deepcopy
138-
global function _BigFloat(prec::Clong, sign::Cint, exp::Clong, d::String)
139-
# ccall-based version, inlined below
140-
#z = new(zero(Clong), zero(Cint), zero(Clong), C_NULL, d)
141-
#ccall((:mpfr_custom_init,libmpfr), Cvoid, (Ptr{Limb}, Clong), d, prec) # currently seems to be a no-op in mpfr
142-
#NAN_KIND = Cint(0)
143-
#ccall((:mpfr_custom_init_set,libmpfr), Cvoid, (Ref{BigFloat}, Cint, Clong, Ptr{Limb}), z, NAN_KIND, prec, d)
144-
#return z
145-
return new(prec, sign, exp, pointer(d), d)
146-
end
145+
global _BigFloat(d::Memory{Limb}) = new(d)
147146

148147
function BigFloat(; precision::Integer=_precision_with_base_2(BigFloat))
149148
precision < 1 && throw(DomainError(precision, "`precision` cannot be less than 1."))
150149
nb = ccall((:mpfr_custom_get_size,libmpfr), Csize_t, (Clong,), precision)
151-
nb = (nb + Core.sizeof(Limb) - 1) ÷ Core.sizeof(Limb) # align to number of Limb allocations required for this
152-
#d = Vector{Limb}(undef, nb)
153-
d = _string_n(nb * Core.sizeof(Limb))
154-
EXP_NAN = mpfr_special_exponent_nan
155-
return _BigFloat(Clong(precision), one(Cint), EXP_NAN, d) # +NAN
150+
nl = (nb + offset_p + sizeof(Limb) - 1) ÷ Core.sizeof(Limb) # align to number of Limb allocations required for this
151+
d = Memory{Limb}(undef, nl % Int)
152+
# ccall-based version, inlined below
153+
z = _BigFloat(d) # initialize to +NAN
154+
#ccall((:mpfr_custom_init,libmpfr), Cvoid, (Ptr{Limb}, Clong), BigFloatData(d), prec) # currently seems to be a no-op in mpfr
155+
#NAN_KIND = Cint(0)
156+
#ccall((:mpfr_custom_init_set,libmpfr), Cvoid, (Ref{BigFloat}, Cint, Clong, Ptr{Limb}), z, NAN_KIND, prec, BigFloatData(d))
157+
z.prec = Clong(precision)
158+
z.sign = one(Cint)
159+
z.exp = mpfr_special_exponent_nan
160+
return z
156161
end
157162
end
158163

159-
# The rounding mode here shouldn't matter.
160-
significand_limb_count(x::BigFloat) = div(sizeof(x._d), sizeof(Limb), RoundToZero)
164+
"""
165+
Segment of raw words of bits interpreted as a big integer. Less
166+
significant words come first. Each word is in machine-native bit-order.
167+
"""
168+
struct BigFloatData{Limb}
169+
d::Memory{Limb}
170+
end
171+
172+
# BigFloat interface
173+
@inline function Base.getproperty(x::BigFloat, s::Symbol)
174+
d = getfield(x, :d)
175+
p = Base.unsafe_convert(Ptr{Limb}, d)
176+
if s === :prec
177+
return GC.@preserve d unsafe_load(Ptr{Clong}(p) + offset_prec)
178+
elseif s === :sign
179+
return GC.@preserve d unsafe_load(Ptr{Cint}(p) + offset_sign)
180+
elseif s === :exp
181+
return GC.@preserve d unsafe_load(Ptr{Clong}(p) + offset_exp)
182+
elseif s === :d
183+
return BigFloatData(d)
184+
else
185+
return throw(FieldError(typeof(x), s))
186+
end
187+
end
188+
189+
@inline function Base.setproperty!(x::BigFloat, s::Symbol, v)
190+
d = getfield(x, :d)
191+
p = Base.unsafe_convert(Ptr{Limb}, d)
192+
if s === :prec
193+
return GC.@preserve d unsafe_store!(Ptr{Clong}(p) + offset_prec, v)
194+
elseif s === :sign
195+
return GC.@preserve d unsafe_store!(Ptr{Cint}(p) + offset_sign, v)
196+
elseif s === :exp
197+
return GC.@preserve d unsafe_store!(Ptr{Clong}(p) + offset_exp, v)
198+
#elseif s === :d # not mutable
199+
else
200+
return throw(FieldError(x, s))
201+
end
202+
end
203+
204+
# Ref interface: make sure the conversion to C is done properly
205+
Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ptr{BigFloat}) = error("not compatible with mpfr")
206+
Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ref{BigFloat}) = error("not compatible with mpfr")
207+
Base.cconvert(::Type{Ref{BigFloat}}, x::BigFloat) = x.d # BigFloatData is the Ref type for BigFloat
208+
function Base.unsafe_convert(::Type{Ref{BigFloat}}, x::BigFloatData)
209+
d = getfield(x, :d)
210+
p = Base.unsafe_convert(Ptr{Limb}, d)
211+
GC.@preserve d unsafe_store!(Ptr{Ptr{Limb}}(p) + offset_d, p + offset_p, :monotonic) # :monotonic ensure that TSAN knows that this isn't a data race
212+
return Ptr{BigFloat}(p)
213+
end
214+
Base.unsafe_convert(::Type{Ptr{Limb}}, fd::BigFloatData) = Base.unsafe_convert(Ptr{Limb}, getfield(fd, :d)) + offset_p
215+
function Base.setindex!(fd::BigFloatData, v, i)
216+
d = getfield(fd, :d)
217+
@boundscheck 1 <= i <= length(d) - offset_p_limbs || throw(BoundsError(fd, i))
218+
@inbounds d[i + offset_p_limbs] = v
219+
return fd
220+
end
221+
function Base.getindex(fd::BigFloatData, i)
222+
d = getfield(fd, :d)
223+
@boundscheck 1 <= i <= length(d) - offset_p_limbs || throw(BoundsError(fd, i))
224+
@inbounds d[i + offset_p_limbs]
225+
end
226+
Base.length(fd::BigFloatData) = length(getfield(fd, :d)) - offset_p_limbs
227+
Base.copyto!(fd::BigFloatData, limbs) = copyto!(getfield(fd, :d), offset_p_limbs + 1, limbs) # for Random
228+
229+
include("rawbigfloats.jl")
161230

162231
rounding_raw(::Type{BigFloat}) = something(Base.ScopedValues.get(CURRENT_ROUNDING_MODE), ROUNDING_MODE[])
163232
setrounding_raw(::Type{BigFloat}, r::MPFRRoundingMode) = ROUNDING_MODE[]=r
164233
function setrounding_raw(f::Function, ::Type{BigFloat}, r::MPFRRoundingMode)
165234
Base.ScopedValues.@with(CURRENT_ROUNDING_MODE => r, f())
166235
end
167236

168-
169237
rounding(::Type{BigFloat}) = convert(RoundingMode, rounding_raw(BigFloat))
170238
setrounding(::Type{BigFloat}, r::RoundingMode) = setrounding_raw(BigFloat, convert(MPFRRoundingMode, r))
171239
setrounding(f::Function, ::Type{BigFloat}, r::RoundingMode) =
172240
setrounding_raw(f, BigFloat, convert(MPFRRoundingMode, r))
173241

174242

175-
# overload the definition of unsafe_convert to ensure that `x.d` is assigned
176-
# it may have been dropped in the event that the BigFloat was serialized
177-
Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ptr{BigFloat}) = x
178-
@inline function Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ref{BigFloat})
179-
x = x[]
180-
if x.d == C_NULL
181-
x.d = pointer(x._d)
182-
end
183-
return convert(Ptr{BigFloat}, Base.pointer_from_objref(x))
184-
end
185-
186243
"""
187244
BigFloat(x::Union{Real, AbstractString} [, rounding::RoundingMode=rounding(BigFloat)]; [precision::Integer=precision(BigFloat)])
188245
@@ -283,17 +340,18 @@ function BigFloat(x::Float64, r::MPFRRoundingMode=rounding_raw(BigFloat); precis
283340
nlimbs = (precision + 8*Core.sizeof(Limb) - 1) ÷ (8*Core.sizeof(Limb))
284341

285342
# Limb is a CLong which is a UInt32 on windows (thank M$) which makes this more complicated and slower.
343+
zd = z.d
286344
if Limb === UInt64
287345
for i in 1:nlimbs-1
288-
unsafe_store!(z.d, 0x0, i)
346+
@inbounds setindex!(zd, 0x0, i)
289347
end
290-
unsafe_store!(z.d, val, nlimbs)
348+
@inbounds setindex!(zd, val, nlimbs)
291349
else
292350
for i in 1:nlimbs-2
293-
unsafe_store!(z.d, 0x0, i)
351+
@inbounds setindex!(zd, 0x0, i)
294352
end
295-
unsafe_store!(z.d, val % UInt32, nlimbs-1)
296-
unsafe_store!(z.d, (val >> 32) % UInt32, nlimbs)
353+
@inbounds setindex!(zd, val % UInt32, nlimbs-1)
354+
@inbounds setindex!(zd, (val >> 32) % UInt32, nlimbs)
297355
end
298356
z
299357
end
@@ -440,12 +498,12 @@ function to_ieee754(::Type{T}, x::BigFloat, rm) where {T<:AbstractFloat}
440498
ret_u = if is_regular & !rounds_to_inf & !rounds_to_zero
441499
if !exp_is_huge_p
442500
# significand
443-
v = RawBigInt{Limb}(x._d, significand_limb_count(x))
501+
v = x.d::BigFloatData
444502
len = max(ieee_precision + min(exp_diff, 0), 0)::Int
445503
signif = truncated(U, v, len) & significand_mask(T)
446504

447505
# round up if necessary
448-
rh = RawBigIntRoundingIncrementHelper(v, len)
506+
rh = BigFloatDataRoundingIncrementHelper(v, len)
449507
incr = correct_rounding_requires_increment(rh, rm, sb)
450508

451509
# exponent
@@ -1193,10 +1251,8 @@ set_emin!(x) = check_exponent_err(ccall((:mpfr_set_emin, libmpfr), Cint, (Clong,
11931251

11941252
function Base.deepcopy_internal(x::BigFloat, stackdict::IdDict)
11951253
get!(stackdict, x) do
1196-
# d = copy(x._d)
1197-
d = x._d
1198-
d′ = GC.@preserve d unsafe_string(pointer(d), sizeof(d)) # creates a definitely-new String
1199-
y = _BigFloat(x.prec, x.sign, x.exp, d′)
1254+
d′ = copy(getfield(x, :d))
1255+
y = _BigFloat(d′)
12001256
#ccall((:mpfr_custom_move,libmpfr), Cvoid, (Ref{BigFloat}, Ptr{Limb}), y, d) # unnecessary
12011257
return y
12021258
end::BigFloat
@@ -1210,7 +1266,8 @@ function decompose(x::BigFloat)::Tuple{BigInt, Int, Int}
12101266
s.size = cld(x.prec, 8*sizeof(Limb)) # limbs
12111267
b = s.size * sizeof(Limb) # bytes
12121268
ccall((:__gmpz_realloc2, libgmp), Cvoid, (Ref{BigInt}, Culong), s, 8b) # bits
1213-
memcpy(s.d, x.d, b)
1269+
xd = x.d
1270+
GC.@preserve xd memcpy(s.d, Base.unsafe_convert(Ptr{Limb}, xd), b)
12141271
s, x.exp - 8b, x.sign
12151272
end
12161273

0 commit comments

Comments
 (0)
Please sign in to comment.