diff --git a/Project.toml b/Project.toml index 29cc1ce..a89dc5c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "FixedPointDecimals" uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0" authors = ["Fengyang Wang ", "Curtis Vogt "] -version = "0.6.3" +version = "0.6.4" [deps] BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" diff --git a/src/FixedPointDecimals.jl b/src/FixedPointDecimals.jl index 1906b88..b4bf731 100644 --- a/src/FixedPointDecimals.jl +++ b/src/FixedPointDecimals.jl @@ -116,12 +116,41 @@ const FD = FixedDecimal include("parse.jl") -function __init__() - nt = isdefined(Base.Threads, :maxthreadid) ? Threads.maxthreadid() : Threads.nthreads() - # Buffers used in parsing when dealing with BigInts, see _divpow10! in parse.jl - resize!(empty!(_BIGINT_10s), nt) - resize!(empty!(_BIGINT_Rs), nt) - return +const _BIGINT1 = BigInt(1) +const _BIGINT2 = BigInt(2) +const _BIGINT10 = BigInt(10) +# Adapted from Parsers.jl, see https://github.com/JuliaData/Parsers.jl/pull/195 +@static if isdefined(Base, :OncePerTask) + const _get_bigint10s = OncePerTask{BigInt}(() -> BigInt(; nbits=256)) + const _get_bigintRs = OncePerTask{BigInt}(() -> BigInt(; nbits=256)) +else + # N.B This code is not thread safe in the presence of thread migration + const _BIGINT_10s = BigInt[] # buffer for "remainders" in _divpow10!, accessed via `access_threaded` + const _BIGINT_Rs = BigInt[] # buffer for "remainders" in _divpow10!, accessed via `access_threaded` + + _get_bigint10s() = access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_10s) + _get_bigintRs() = access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_Rs) + + function access_threaded(f, v::Vector) + tid = Threads.threadid() + 0 < tid <= length(v) || _length_assert() + if @inbounds isassigned(v, tid) + @inbounds x = v[tid] + else + x = f() + @inbounds v[tid] = x + end + return x + end + @noinline _length_assert() = @assert false "0 < tid <= v" + + function __init__() + nt = isdefined(Base.Threads, :maxthreadid) ? Threads.maxthreadid() : Threads.nthreads() + # Buffers used in parsing when dealing with BigInts, see _divpow10! in parse.jl + resize!(empty!(_BIGINT_10s), nt) + resize!(empty!(_BIGINT_Rs), nt) + return + end end # Custom widemul implementation to avoid the cost of widening to BigInt. diff --git a/src/parse.jl b/src/parse.jl index 5f68c34..41976fb 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -31,12 +31,6 @@ const OPTIONS_ROUND_THROWS = Parsers.Options(rounding=nothing) # TODO: a lookup table per type would be faster @inline _shift(n::T, decpos) where {T} = T(10)^decpos * n -const _BIGINT1 = BigInt(1) -const _BIGINT2 = BigInt(2) -const _BIGINT10 = BigInt(10) -const _BIGINT_10s = BigInt[] # buffer for "remainders" in _divpow10!, accessed via `Parsers.access_threaded` -const _BIGINT_Rs = BigInt[] # buffer for "remainders" in _divpow10!, accessed via `Parsers.access_threaded` - for T in (Base.BitSigned_types..., Base.BitUnsigned_types...) let bytes = Tuple(codeunits(string(typemax(T)))) # The number of digits an integer of type T can hold @@ -74,8 +68,8 @@ function _divpow10!(x::T, code, pow, ::RoundingMode{:Throw}) where {T} end function _divpow10!(x::BigInt, code, pow, ::RoundingMode{:Nearest}) # adapted from https://github.com/JuliaLang/julia/blob/112554e1a533cebad4cb0daa27df59636405c075/base/div.jl#L217 - @inbounds r = Parsers.access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_Rs) # we must not yield here! - @inbounds y = Parsers.access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_10s) # we must not yield here! + r = _get_bigintRs() # we must not yield here! + y = _get_bigint10s() # we must not yield here! Base.GMP.MPZ.set!(y, _BIGINT10) # y = 10 Base.GMP.MPZ.pow_ui!(y, pow) # y = y^pow Base.GMP.MPZ.tdiv_qr!(x, r, x, y) # x, r = divrem(x, y) @@ -87,7 +81,7 @@ function _divpow10!(x::BigInt, code, pow, ::RoundingMode{:Nearest}) return x, code end function _divpow10!(x::BigInt, code, pow, ::RoundingMode{:ToZero}) - @inbounds y = Parsers.access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_10s) # we must not yield here! + y = _get_bigint10s() # we must not yield here! Base.GMP.MPZ.set!(y, _BIGINT10) # y = 10 Base.GMP.MPZ.pow_ui!(y, pow) # y = y^pow Base.GMP.MPZ.tdiv_q!(x, y) # x = div(x, y) @@ -95,7 +89,7 @@ function _divpow10!(x::BigInt, code, pow, ::RoundingMode{:ToZero}) end function _divpow10!(x::BigInt, code, pow, ::RoundingMode{:Throw}) - @inbounds y = Parsers.access_threaded(() -> (@static VERSION > v"1.5" ? BigInt(; nbits=256) : BigInt()), _BIGINT_10s) # we must not yield here! + y = _get_bigint10s() # we must not yield here! Base.GMP.MPZ.set!(y, _BIGINT10) # y = 10 Base.GMP.MPZ.pow_ui!(y, pow) # y = y^pow Base.GMP.MPZ.tdiv_qr!(x, y, x, y) # x, y = divrem(x, y)