Skip to content

Commit a1f6448

Browse files
authored
faster rand!(::MersenneTwister, ::Array{Bool}) (#33721)
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).
1 parent bb2aa52 commit a1f6448

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

NEWS.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,12 @@ Standard library changes
184184
* `randn!(::MersenneTwister, ::Array{Float64})` is faster, and as a result, for a given state of the RNG,
185185
the corresponding generated numbers have changed ([#35078]).
186186

187+
* `rand!(::MersenneTwister, ::Array{Bool})` is faster, and as a result, for a given state of the RNG,
188+
the corresponding generated numbers have changed ([#33721]).
189+
187190
* A new faster algorithm ("nearly division less") is used for generating random numbers
188-
within a range ([#29240]).
189-
As a result, the streams of generated numbers is changed.
191+
within a range ([#29240]). As a result, the streams of generated numbers are changed
192+
(for ranges, like in `rand(1:9)`, and for collections in general, like in `rand([1, 2, 3])`).
190193
Also, for performance, the undocumented property that, given a seed and `a, b` of type `Int`,
191194
`rand(a:b)` produces the same stream on 32 and 64 bits architectures, is dropped.
192195

stdlib/Random/src/RNGs.jl

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,10 @@ function rand!(r::MersenneTwister, A::UnsafeView{UInt128}, ::SamplerType{UInt128
586586
end
587587

588588
for T in BitInteger_types
589-
@eval rand!(r::MersenneTwister, A::Array{$T}, sp::SamplerType{$T}) =
590-
(GC.@preserve A rand!(r, UnsafeView(pointer(A), length(A)), sp); A)
589+
@eval function rand!(r::MersenneTwister, A::Array{$T}, sp::SamplerType{$T})
590+
GC.@preserve A rand!(r, UnsafeView(pointer(A), length(A)), sp)
591+
A
592+
end
591593

592594
T == UInt128 && continue
593595

@@ -602,6 +604,52 @@ for T in BitInteger_types
602604
end
603605
end
604606

607+
608+
#### arrays of Bool
609+
610+
# similar to Array{UInt8}, but we need to mask the result so that only the LSB
611+
# in each byte can be non-zero
612+
613+
function rand!(r::MersenneTwister, A1::Array{Bool}, sp::SamplerType{Bool})
614+
n1 = length(A1)
615+
n128 = n1 ÷ 16
616+
617+
if n128 == 0
618+
bits = rand(r, UInt52Raw())
619+
else
620+
GC.@preserve A1 begin
621+
A = UnsafeView{UInt128}(pointer(A1), n128)
622+
rand!(r, UnsafeView{Float64}(A.ptr, 2*n128), CloseOpen12())
623+
# without masking, non-zero bits could be observed in other
624+
# positions than the LSB of each byte
625+
mask = 0x01010101010101010101010101010101
626+
# we need up to 15 bits of entropy in `bits` for the final loop,
627+
# which we will extract from x = A[1] % UInt64;
628+
# let y = x % UInt32; y contains 32 bits of entropy, but 4
629+
# of them will be used for A[1] itself (the first of
630+
# each byte). To compensate, we xor with (y >> 17),
631+
# which gets the entropy from the second bit of each byte
632+
# of the upper-half of y, and sets it in the first bit
633+
# of each byte of the lower half; the first two bytes
634+
# now contain 16 usable random bits
635+
x = A[1] % UInt64
636+
bits = x x >> 17
637+
for i = 1:n128
638+
# << 5 to randomize the first bit of the 8th & 16th byte
639+
# (i.e. we move bit 52 (resp. 52 + 64), which is unused,
640+
# to position 57 (resp. 57 + 64))
641+
A[i] = (A[i] A[i] << 5) & mask
642+
end
643+
end
644+
end
645+
for i = 16*n128+1:n1
646+
@inbounds A1[i] = bits % Bool
647+
bits >>= 1
648+
end
649+
A1
650+
end
651+
652+
605653
### randjump
606654

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

0 commit comments

Comments
 (0)