Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "FixedPointDecimals"
uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0"
authors = ["Fengyang Wang <[email protected]>", "Curtis Vogt <[email protected]>"]
version = "0.6.3"
version = "0.6.4"

[deps]
BitIntegers = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1"
Expand Down
41 changes: 35 additions & 6 deletions src/FixedPointDecimals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 4 additions & 10 deletions src/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -87,15 +81,15 @@ 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)
return x, code
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!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've dropped the @inbounds here -- tbh i can't see where there would be bounds-checking that this disables, but perhaps there is somewhere in a nested call? (or maybe access_threaded meant to have a @boundscheck but didn't?)

probably it's fine to drop (but i wish this repo had benchmarks)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that access_threaded already elides bounds checking, so this should be fine

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)
Expand Down
Loading