Skip to content

Commit

Permalink
faster rand!(::MersenneTwister, ::Array{Bool}) (JuliaLang#33721)
Browse files Browse the repository at this point in the history
This uses the same optimizations as for other bits types,
and gives equivalent performance as for `UInt8` (at least
7x to 9x speedup in few tested cases).
  • Loading branch information
rfourquet authored May 4, 2020
1 parent bb2aa52 commit a1f6448
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 4 deletions.
7 changes: 5 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,12 @@ Standard library changes
* `randn!(::MersenneTwister, ::Array{Float64})` is faster, and as a result, for a given state of the RNG,
the corresponding generated numbers have changed ([#35078]).

* `rand!(::MersenneTwister, ::Array{Bool})` is faster, and as a result, for a given state of the RNG,
the corresponding generated numbers have changed ([#33721]).

* A new faster algorithm ("nearly division less") is used for generating random numbers
within a range ([#29240]).
As a result, the streams of generated numbers is changed.
within a range ([#29240]). As a result, the streams of generated numbers are changed
(for ranges, like in `rand(1:9)`, and for collections in general, like in `rand([1, 2, 3])`).
Also, for performance, the undocumented property that, given a seed and `a, b` of type `Int`,
`rand(a:b)` produces the same stream on 32 and 64 bits architectures, is dropped.

Expand Down
52 changes: 50 additions & 2 deletions stdlib/Random/src/RNGs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,10 @@ function rand!(r::MersenneTwister, A::UnsafeView{UInt128}, ::SamplerType{UInt128
end

for T in BitInteger_types
@eval rand!(r::MersenneTwister, A::Array{$T}, sp::SamplerType{$T}) =
(GC.@preserve A rand!(r, UnsafeView(pointer(A), length(A)), sp); A)
@eval function rand!(r::MersenneTwister, A::Array{$T}, sp::SamplerType{$T})
GC.@preserve A rand!(r, UnsafeView(pointer(A), length(A)), sp)
A
end

T == UInt128 && continue

Expand All @@ -602,6 +604,52 @@ for T in BitInteger_types
end
end


#### arrays of Bool

# similar to Array{UInt8}, but we need to mask the result so that only the LSB
# in each byte can be non-zero

function rand!(r::MersenneTwister, A1::Array{Bool}, sp::SamplerType{Bool})
n1 = length(A1)
n128 = n1 ÷ 16

if n128 == 0
bits = rand(r, UInt52Raw())
else
GC.@preserve A1 begin
A = UnsafeView{UInt128}(pointer(A1), n128)
rand!(r, UnsafeView{Float64}(A.ptr, 2*n128), CloseOpen12())
# without masking, non-zero bits could be observed in other
# positions than the LSB of each byte
mask = 0x01010101010101010101010101010101
# we need up to 15 bits of entropy in `bits` for the final loop,
# which we will extract from x = A[1] % UInt64;
# let y = x % UInt32; y contains 32 bits of entropy, but 4
# of them will be used for A[1] itself (the first of
# each byte). To compensate, we xor with (y >> 17),
# which gets the entropy from the second bit of each byte
# of the upper-half of y, and sets it in the first bit
# of each byte of the lower half; the first two bytes
# now contain 16 usable random bits
x = A[1] % UInt64
bits = x x >> 17
for i = 1:n128
# << 5 to randomize the first bit of the 8th & 16th byte
# (i.e. we move bit 52 (resp. 52 + 64), which is unused,
# to position 57 (resp. 57 + 64))
A[i] = (A[i] A[i] << 5) & mask
end
end
end
for i = 16*n128+1:n1
@inbounds A1[i] = bits % Bool
bits >>= 1
end
A1
end


### randjump

# Old randjump methods are deprecated, the scalar version is in the Future module.
Expand Down

0 comments on commit a1f6448

Please sign in to comment.