Skip to content

Commit cd2ebf9

Browse files
authored
speed-up randperm by using our current rand(1:n) (#50509)
And similarly for `randcycle` and `shuffle`. We had a custom version of range generation for `randperm`, which was based on the ideas of our previous default range sampler `SamplerRangeFast` (generate `k`-bits integers using masking and reject out-of-range ones) and took advantage of the fact that `randperm` needs to generate `rand(1:i)` for `i = 2:n`. But our current range sampler ("Nearly Division Less") is usually better than this hack, and makes these functions more readable. Typically, for array lengths `< 2^20`, the new version is faster, but gets slightly slower beyond 2^22. Here are some speedups: ![randperm-ndl-speedup](https://github.com/JuliaLang/julia/assets/8462914/b999ba43-e588-49f4-91a2-a4b23e79716d) The slow down for big arrays seems fine to me, but I will see if I can find an easy workaround. Fix #57771.
1 parent 0f9b2fc commit cd2ebf9

File tree

1 file changed

+39
-59
lines changed

1 file changed

+39
-59
lines changed

stdlib/Random/src/misc.jl

Lines changed: 39 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,6 @@ julia> randsubseq(Xoshiro(123), 1:8, 0.3)
176176
randsubseq(A::AbstractArray, p::Real) = randsubseq(default_rng(), A, p)
177177

178178

179-
## rand Less Than Masked 52 bits (helper function)
180-
181-
"Return a sampler generating a random `Int` (masked with `mask`) in ``[0, n)``, when `n <= 2^52`."
182-
ltm52(n::Int, mask::Int=nextpow(2, n)-1) = LessThan(n-1, Masked(mask, UInt52Raw(Int)))
183-
184179
## shuffle & shuffle!
185180

186181
function shuffle(rng::AbstractRNG, tup::NTuple{N}) where {N}
@@ -219,31 +214,22 @@ optionally supplying the random-number generator `rng`.
219214
220215
# Examples
221216
```jldoctest
222-
julia> shuffle!(Xoshiro(123), Vector(1:10))
223-
10-element Vector{Int64}:
224-
5
225-
4
226-
2
227-
3
228-
6
229-
10
230-
8
231-
1
232-
9
233-
7
217+
julia> shuffle!(Xoshiro(0), Vector(1:6))
218+
6-element Vector{Int64}:
219+
5
220+
1
221+
2
222+
6
223+
3
224+
4
234225
```
235226
"""
236-
function shuffle!(r::AbstractRNG, a::AbstractArray)
227+
function shuffle!(rng::AbstractRNG, a::AbstractArray)
237228
# keep it consistent with `randperm!` and `randcycle!` if possible
238229
require_one_based_indexing(a)
239-
n = length(a)
240-
@assert n <= Int64(2)^52
241-
n == 0 && return a
242-
mask = 3
243-
@inbounds for i = 2:n
244-
j = 1 + rand(r, ltm52(i, mask))
230+
@inbounds for i = 2:length(a)
231+
j = rand(rng, 1:i)
245232
a[i], a[j] = a[j], a[i]
246-
i == 1 + mask && (mask = 2 * mask + 1)
247233
end
248234
return a
249235
end
@@ -278,18 +264,14 @@ indices, see [`randperm`](@ref).
278264
279265
# Examples
280266
```jldoctest
281-
julia> shuffle(Xoshiro(123), Vector(1:10))
282-
10-element Vector{Int64}:
283-
5
284-
4
285-
2
286-
3
287-
6
288-
10
289-
8
290-
1
291-
9
292-
7
267+
julia> shuffle(Xoshiro(0), 1:6)
268+
6-element Vector{Int64}:
269+
5
270+
1
271+
2
272+
6
273+
3
274+
4
293275
```
294276
"""
295277
function shuffle end
@@ -318,12 +300,14 @@ To randomly permute an arbitrary vector, see [`shuffle`](@ref) or
318300
319301
# Examples
320302
```jldoctest
321-
julia> randperm(Xoshiro(123), 4)
322-
4-element Vector{Int64}:
303+
julia> randperm(Xoshiro(0), 6)
304+
6-element Vector{Int64}:
305+
5
323306
1
324-
4
325307
2
308+
6
326309
3
310+
4
327311
```
328312
"""
329313
randperm(r::AbstractRNG, n::T) where {T <: Integer} = randperm!(r, Vector{T}(undef, n))
@@ -342,29 +326,28 @@ optional `rng` argument specifies a random number generator (see
342326
343327
# Examples
344328
```jldoctest
345-
julia> randperm!(Xoshiro(123), Vector{Int}(undef, 4))
346-
4-element Vector{Int64}:
329+
julia> randperm!(Xoshiro(0), Vector{Int}(undef, 6))
330+
6-element Vector{Int64}:
331+
5
347332
1
348-
4
349333
2
334+
6
350335
3
336+
4
351337
```
352338
"""
353-
function randperm!(r::AbstractRNG, a::AbstractArray{<:Integer})
339+
function randperm!(rng::AbstractRNG, a::AbstractArray{<:Integer})
354340
# keep it consistent with `shuffle!` and `randcycle!` if possible
355341
Base.require_one_based_indexing(a)
356342
n = length(a)
357-
@assert n <= Int64(2)^52
358343
n == 0 && return a
359344
a[1] = 1
360-
mask = 3
361345
@inbounds for i = 2:n
362-
j = 1 + rand(r, ltm52(i, mask))
346+
j = rand(rng, 1:i)
363347
if i != j # a[i] is undef (and could be #undef)
364348
a[i] = a[j]
365349
end
366350
a[j] = i
367-
i == 1 + mask && (mask = 2 * mask + 1)
368351
end
369352
return a
370353
end
@@ -393,14 +376,14 @@ which are sampled uniformly. If `n == 0`, `randcycle` returns an empty vector.
393376
394377
# Examples
395378
```jldoctest
396-
julia> randcycle(Xoshiro(123), 6)
379+
julia> randcycle(Xoshiro(0), 6)
397380
6-element Vector{Int64}:
398381
5
382+
1
399383
4
400-
2
401384
6
402385
3
403-
1
386+
2
404387
```
405388
"""
406389
randcycle(r::AbstractRNG, n::T) where {T <: Integer} = randcycle!(r, Vector{T}(undef, n))
@@ -424,30 +407,27 @@ which are sampled uniformly. If `A` is empty, `randcycle!` leaves it unchanged.
424407
425408
# Examples
426409
```jldoctest
427-
julia> randcycle!(Xoshiro(123), Vector{Int}(undef, 6))
410+
julia> randcycle!(Xoshiro(0), Vector{Int}(undef, 6))
428411
6-element Vector{Int64}:
429412
5
413+
1
430414
4
431-
2
432415
6
433416
3
434-
1
417+
2
435418
```
436419
"""
437-
function randcycle!(r::AbstractRNG, a::AbstractArray{<:Integer})
420+
function randcycle!(rng::AbstractRNG, a::AbstractArray{<:Integer})
438421
# keep it consistent with `shuffle!` and `randperm!` if possible
439422
Base.require_one_based_indexing(a)
440423
n = length(a)
441-
@assert n <= Int64(2)^52
442424
n == 0 && return a
443425
a[1] = 1
444-
mask = 3
445426
# Sattolo's algorithm:
446427
@inbounds for i = 2:n
447-
j = 1 + rand(r, ltm52(i-1, mask))
428+
j = rand(rng, 1:i-1)
448429
a[i] = a[j]
449430
a[j] = i
450-
i == 1 + mask && (mask = 2 * mask + 1)
451431
end
452432
return a
453433
end

0 commit comments

Comments
 (0)