Skip to content

Commit

Permalink
mpfr: speed improvements
Browse files Browse the repository at this point in the history
Unlike GMP / BigInt, the MPFR library exposes an interface for external allocation:
http://www.mpfr.org/mpfr-current/mpfr.html#Custom-Interface

Using that allows us to use our fast memory-pool gc
and avoid adding finalizers and use the slow malloc/free functions.
(and in some cases, some of it might even end up on the stack!)

Also switch to allocating lgamma_sign on the stack, for better performance (and thread-safety)

Also fixes Serialization to preserve BigFloat precision.
  • Loading branch information
vtjnash committed Jun 11, 2018
1 parent 01a0964 commit 2644f79
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 28 deletions.
56 changes: 40 additions & 16 deletions base/mpfr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import
cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh,
cbrt, typemax, typemin, unsafe_trunc, realmin, realmax, rounding,
setrounding, maxintfloat, widen, significand, frexp, tryparse, iszero,
isone, big, RefValue
isone, big, _string_n

import .Base.Rounding: rounding_raw, setrounding_raw

Expand All @@ -39,8 +39,8 @@ function __init__()
nothing
end

const ROUNDING_MODE = RefValue{Cint}(0)
const DEFAULT_PRECISION = RefValue(256)
const ROUNDING_MODE = Ref{Cint}(0) # actually an Enum, defined by `to_mpfr`
const DEFAULT_PRECISION = Ref{Int}(256)

# Basic type and initialization definitions

Expand All @@ -54,21 +54,44 @@ mutable struct BigFloat <: AbstractFloat
sign::Cint
exp::Clong
d::Ptr{Limb}
# _d::Buffer{Limb} # Julia gc handle for memory @ d
_d::String # Julia gc handle for memory @ d (optimized)

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

function BigFloat()
prec = precision(BigFloat)
z = new(zero(Clong), zero(Cint), zero(Clong), C_NULL)
ccall((:mpfr_init2,:libmpfr), Cvoid, (Ref{BigFloat}, Clong), z, prec)
finalizer(cglobal((:mpfr_clear, :libmpfr)), z)
return z
prec::Clong = precision(BigFloat)
nb = ccall((:mpfr_custom_get_size,:libmpfr), Csize_t, (Clong,), prec)
nb = (nb + Core.sizeof(Limb) - 1) ÷ Core.sizeof(Limb) # align to number of Limb allocations required for this
#d = Vector{Limb}(undef, nb)
d = _string_n(nb * Core.sizeof(Limb))
EXP_NAN = Clong(1) - Clong(typemax(Culong) >> 1)
return _BigFloat(prec, one(Cint), EXP_NAN, d) # +NAN
end
end

# Not recommended for general use:
function BigFloat(prec::Clong, sign::Cint, exp::Clong, d::Ptr{Cvoid})
new(prec, sign, exp, d)
# overload the definition of unsafe_convert to ensure that `x.d` is assigned
# it may have been dropped in the event that the BigFloat was serialized
Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ptr{BigFloat}) = x
@inline function Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ref{BigFloat})
x = x[]
if x.d == C_NULL
x.d = pointer(x._d)
end
return convert(Ptr{BigFloat}, Base.pointer_from_objref(x))
end


"""
BigFloat(x)
Expand Down Expand Up @@ -739,6 +762,7 @@ function setprecision(::Type{BigFloat}, precision::Int)
throw(DomainError(precision, "`precision` cannot be less than 2."))
end
DEFAULT_PRECISION[] = precision
return precision
end

setprecision(precision::Int) = setprecision(BigFloat, precision)
Expand Down Expand Up @@ -957,11 +981,11 @@ set_emin!(x) = ccall((:mpfr_set_emin, :libmpfr), Cvoid, (Clong,), x)

function Base.deepcopy_internal(x::BigFloat, stackdict::IdDict)
haskey(stackdict, x) && return stackdict[x]
prec = precision(x)
y = BigFloat(zero(Clong), zero(Cint), zero(Clong), C_NULL)
ccall((:mpfr_init2,:libmpfr), Cvoid, (Ref{BigFloat}, Clong), y, prec)
finalizer(cglobal((:mpfr_clear, :libmpfr)), y)
ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), y, x, ROUNDING_MODE[])
# d = copy(x._d)
d = x._d
d′ = GC.@preserve d unsafe_string(pointer(d), sizeof(d)) # creates a definitely-new String
y = _BigFloat(x.prec, x.sign, x.exp, d′)
#ccall((:mpfr_custom_move,:libmpfr), Cvoid, (Ref{BigFloat}, Ptr{Limb}), y, d) # unnecessary
stackdict[x] = y
return y
end
Expand Down
7 changes: 0 additions & 7 deletions stdlib/Serialization/src/Serialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,6 @@ function serialize(s::AbstractSerializer, n::BigInt)
serialize(s, string(n, base = 62))
end

function serialize(s::AbstractSerializer, n::BigFloat)
serialize_type(s, BigFloat)
serialize(s, string(n))
end

function serialize(s::AbstractSerializer, ex::Expr)
serialize_cycle(s, ex) && return
l = length(ex.args)
Expand Down Expand Up @@ -1184,8 +1179,6 @@ function deserialize(s::AbstractSerializer, T::Type{Dict{K,V}}) where {K,V}
return t
end

deserialize(s::AbstractSerializer, ::Type{BigFloat}) = parse(BigFloat, deserialize(s))

deserialize(s::AbstractSerializer, ::Type{BigInt}) = parse(BigInt, deserialize(s), base = 62)

function deserialize(s::AbstractSerializer, t::Type{Regex})
Expand Down
16 changes: 11 additions & 5 deletions test/mpfr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -853,11 +853,17 @@ end
@test_throws ArgumentError parse(BigFloat, "1\0")

@testset "serialization (issue #12386)" begin
b = IOBuffer()
x = 2.1 * big(pi)
serialize(b, x)
seekstart(b)
@test deserialize(b) == x
b = PipeBuffer()
let x = setprecision(53) do
return 2.1 * big(pi)
end
serialize(b, x)
@test deserialize(b) == x
end
let x = BigFloat(Inf, 46)
serialize(b, x)
@test deserialize(b) == x == BigFloat(Inf, 2)
end
end
@test isnan(sqrt(BigFloat(NaN)))

Expand Down
7 changes: 7 additions & 0 deletions test/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ try
g() = override(1.0)
Test.@test g() === 2.0 # compile this
const abigfloat_f() = big"12.34"
const abigfloat_x = big"43.21"
end
""")
@test_throws ErrorException Core.kwfunc(Base.nothing) # make sure `nothing` didn't have a kwfunc (which would invalidate the attempted test)
Expand All @@ -164,6 +167,10 @@ try
@test Foo.override(1.0e0) == Float64('a')
@test Foo.override(1.0f0) == 'b'
@test Foo.override(UInt(1)) == 2

# Issue #15722
@test Foo.abigfloat_f()::BigFloat == big"12.34"
@test (Foo.abigfloat_x::BigFloat + 21) == big"64.21"
end

cachedir = joinpath(dir, "compiled", "v$(VERSION.major).$(VERSION.minor)")
Expand Down

0 comments on commit 2644f79

Please sign in to comment.