Skip to content

Commit 6498664

Browse files
authored
faster rand(1:n) by outlining unlikely branch (#58089)
It's hard to measure the improvement with single calls, but this change substantially improve the situation in #50509, such that these new versions of `randperm` etc are almost always faster (even for big n). Here are some example benchmarks. Note that biggest ranges like `UInt(0):UInt(2)^64-2` are the ones exercising the most the "unlikely" branch: ```julia julia> const xx = Xoshiro(); using Chairmarks julia> rands(rng, ns) = for i=ns rand(rng, zero(i):i) end julia> rands(ns) = for i=ns rand(zero(i):i) end julia> @b rand(xx, 1:100), rand(xx, UInt(0):UInt(2)^63), rand(xx, UInt(0):UInt(2)^64-3), rand(xx, UInt(0):UInt(2)^64-2), rand(xx, UInt(0):UInt(2)^64-1) (1.968 ns, 8.000 ns, 3.321 ns, 3.321 ns, 2.152 ns) # PR (2.151 ns, 7.284 ns, 2.151 ns, 2.151 ns, 2.151 ns) # master julia> @b rand(1:100), rand(UInt(0):UInt(2)^63), rand(UInt(0):UInt(2)^64-3), rand(UInt(0):UInt(2)^64-2),rand(UInt(0):UInt(2)^64-1) # with TaskLocalRNG (2.148 ns, 7.837 ns, 3.317 ns, 3.085 ns, 1.957 ns) # PR (3.128 ns, 8.275 ns, 3.324 ns, 3.324 ns, 1.955 ns) # master julia> rands(xx, 1:100), rands(xx, UInt(2)^62:UInt(2)^59:UInt(2)^64-1), rands(xx, UInt(2)^64-4:UInt(2)^64-2) (95.315 ns, 132.144 ns, 7.486 ns) # PR (217.169 ns, 143.519 ns, 8.065 ns) # master julia> rands(1:100), rands(UInt(2)^62:UInt(2)^59:UInt(2)^64-1), rands(UInt(2)^64-4:UInt(2)^64-2) (235.882 ns, 162.809 ns, 10.603 ns) # PR (202.524 ns, 132.869 ns, 7.631 ns) # master ``` So it's a bit tricky: with an explicit RNG, `rands(xx, 1:100)` becomes much faster, but without, `rands(1:100)` becomes slower. Assuming #50509 was merged, `shuffle` is a good function to benchmark `rand(1:n)`, and the changes here consistently improve performance, as shown by this graph (when `TaskLocalRNG` is mentioned, it means *no* RNG argument was passed to the function): ![new-rand-ndl](https://github.com/user-attachments/assets/b7a6229a-f5d9-408e-9102-4056b796d22c) So although there can be slowdowns, I think this change is overall a win.
1 parent ec70a2c commit 6498664

File tree

2 files changed

+14
-10
lines changed

2 files changed

+14
-10
lines changed

stdlib/Random/src/generation.jl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -375,16 +375,20 @@ function rand(rng::AbstractRNG, sp::SamplerRangeNDL{U,T}) where {U,T}
375375
s = sp.s
376376
x = widen(rand(rng, U))
377377
m = x * s
378-
l = m % U
379-
if l < s
380-
t = mod(-s, s) # as s is unsigned, -s is equal to 2^L - s in the paper
381-
while l < t
382-
x = widen(rand(rng, U))
383-
m = x * s
384-
l = m % U
385-
end
378+
r::T = (m % U) < s ? rand_unlikely(rng, s, m) % T :
379+
iszero(s) ? x % T :
380+
(m >> (8*sizeof(U))) % T
381+
r + sp.a
382+
end
383+
384+
# similar to `randn_unlikely` : splitting this unlikely path out results in faster code
385+
@noinline function rand_unlikely(rng, s::U, m)::U where {U}
386+
t = mod(-s, s) # as s is unsigned, -s is equal to 2^L - s in the paper
387+
while (m % U) < t
388+
x = widen(rand(rng, U))
389+
m = x * s
386390
end
387-
(s == 0 ? x : m >> (8*sizeof(U))) % T + sp.a
391+
(m >> (8*sizeof(U))) % U
388392
end
389393

390394

test/testhelpers/coverage_file.info

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SF:<FILENAME>
22
DA:3,1
3-
DA:4,1
3+
DA:4,2
44
DA:5,0
55
DA:7,1
66
DA:8,1

0 commit comments

Comments
 (0)