Skip to content

Commit b2a75e2

Browse files
lorenteyairspeedswift
authored andcommitted
[4.2][SE-0202][stdlib] Random unification (swiftlang#16495)
* Add shims for stdlib random Initial random api Use C syscall for I/O 1. Fixed an issue where integers would would result in an infinite loop if they were unsigned, or signed integers always returning negative numbers. 2. Fixed an issue with Bool initialization Add shuffle functions Add documentation to Random API Fix a few typos within the documentation Fixes more typos Also states that the range for floating points is from 0 to 1 inclusive Update API to reflect mailing list discussions Remove unnecessary import Make sure not to return upperBound on Range Use SecRandomCopyBytes on older macOS Update API to match mailing list discussion, add tests Added pick(_:) to collection Added random(in:using:) to Randomizable Added tests Fix typo in Randomizable documentation Rename pick to sampling Move sampling below random Update docs Use new Libc naming Fix Random.swift with new Libc naming Remove sampling gybify signed integer creation Make FloatingPoint.random exclusive Refactor {Closed}Range.random Fix FloatingPoint initialization Precondition getting a random number from range Fix some doc typos Make .random a function Update API to reflect discussion Make .random a function Remove .random() in favor of .random(in:) for all numeric types Fix compile errors Clean up _stdlib_random Cleanup around API Remove `.random()` requirement from `Collection` Use generators Optimize shuffle() Thread safety for /dev/urandom Remove {Closed}Range<BinaryFloatingPoint>.random() Add Collection random requirement Refactor _stdlib_random Remove whitespace changes Clean linux shim Add shuffle and more tests Provide finishing tests and suggestions Remove refs to Countable ranges Revert to checking if T is > UInt64 (cherry picked from commit d23d219) * Add `_stdlib_random` for more platforms (#1) * Remove refs to Countable ranges * Add `_stdlib_random` for more platforms * Use `getrandom` (if available) for Android, Cygwin * Reorder the `_stdlib_random` functions * Also include <features.h> on Linux * Add `#error TODO` in `_stdlib_random` for Windows * Colon after Fatal Error Performance improvement for Random gybify ranges Fix typo in 'basic random numbers' Add _stdlib_random as a testable method Switch to generic constraints Hopefully link against bcrypt Fix some implementation details 1. Uniform distribution is now uniform 2. Apply Jens' method for uniform floats Fix a lineable attribute (cherry picked from commit a5df0ef) * Revise documentation, add benchmarks (#3) * [stdlib] Revise documentation for new random APIs * [stdlib] Fix constraints on random integer generation * [test] Isolate failing Random test * [benchmark] Add benchmarks for new random APIs Fix Float80 test Value type generators random -> randomElement Fix some docs One more doc fix Doc fixes & bool fix Use computed over explicit (cherry picked from commit f146d17) * Consolidate `_stdlib_random` functions (#2) * Use the `__has_include` and `GRND_RANDOM` macros * Use `getentropy` instead of `getrandom` * Use `std::min` from the <algorithm> header * Move `#if` out of the `_stdlib_random` function * Use `getrandom` with "/dev/urandom" fallback * Use `#pragma comment` to import "Bcrypt.lib" * <https://docs.microsoft.com/en-us/cpp/preprocessor/comment-c-cpp> * <https://clang.llvm.org/docs/UsersManual.html#microsoft-extensions> * Use "/dev/urandom" instead of `SecRandomCopyBytes` * Use `swift::StaticMutex` for shared "/dev/urandom" * Add `getrandom_available`; use `O_CLOEXEC` flag Add platform impl docs Update copyrights Fix docs Add _stdlib_random test Update _stdlib_random test Add missing & Notice about _stdlib_random Fix docs Guard on upperBound = 0 Test full range of 8 bit integers Remove some gyb Clean up integerRangeTest Remove FixedWidthInteger constraint Use arc4random universally Fix randomElement Constrain shuffle to RandomAccessCollection warning instead of error Move Apple's implementation Fix failing test on 32 bit systems (cherry picked from commit b65d0c1) * [stdlib] Add & implement Random._fill(bytes:) requirement (cherry picked from commit 12a2b32) * [stdlib] Random: don't randomize FixedWidthIntegers by overwriting their raw memory Custom FixedWidthInteger types may not support this. Introduce a new (non-public) FixedWidthInteger requirement for generating random values; implement it using &<</+ in the generic case, and specialize it using RandomNumberGenerator._fill(bytes) for the builtin types. (cherry picked from commit 54b3b8b) * [test] Move Random tests under validation-test StdlibCollectionUnittest is not available in smoke tests. (cherry picked from commit c03fe15) * [test] Non-determinism down to 0.999999 (1-in-a-million) rather than 0.999. With the number of tests Swift does, this had a relatively high chance to fail regularly somewhere. Also, rejecting the lower tail means rejecting things that are perfectly uniform, which I don't think should be the purpose of this test. * [test] Float80 only exists on some platforms.
1 parent 19513b0 commit b2a75e2

File tree

15 files changed

+1188
-19
lines changed

15 files changed

+1188
-19
lines changed

benchmark/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ set(SWIFT_BENCH_MODULES
122122
single-source/Queue
123123
single-source/RC4
124124
single-source/RGBHistogram
125+
single-source/RandomShuffle
126+
single-source/RandomValues
125127
single-source/RangeAssignment
126128
single-source/RangeIteration
127129
single-source/RangeReplaceableCollectionPlusDefault
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===--- RandomShuffle.swift ----------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import TestsUtils
14+
15+
//
16+
// Benchmark that shuffles arrays of integers. Measures the performance of
17+
// shuffling large arrays.
18+
//
19+
20+
public let RandomShuffle = [
21+
BenchmarkInfo(name: "RandomShuffleDef", runFunction: run_RandomShuffleDef,
22+
tags: [.api], setUpFunction: setup_RandomShuffle),
23+
BenchmarkInfo(name: "RandomShuffleLCG", runFunction: run_RandomShuffleLCG,
24+
tags: [.api], setUpFunction: setup_RandomShuffle),
25+
]
26+
27+
/// A linear congruential PRNG.
28+
struct LCRNG: RandomNumberGenerator {
29+
private var state: UInt64
30+
31+
init(seed: Int) {
32+
state = UInt64(truncatingIfNeeded: seed)
33+
for _ in 0..<10 { _ = next() }
34+
}
35+
36+
mutating func next() -> UInt64 {
37+
state = 2862933555777941757 &* state &+ 3037000493
38+
return state
39+
}
40+
}
41+
42+
var numbers = Array(0...3_000_000)
43+
44+
@inline(never)
45+
func setup_RandomShuffle() {
46+
_ = numbers.count
47+
}
48+
49+
@inline(never)
50+
public func run_RandomShuffleDef(_ N: Int) {
51+
for _ in 0 ..< N {
52+
numbers.shuffle()
53+
blackHole(numbers.first!)
54+
}
55+
}
56+
57+
@inline(never)
58+
public func run_RandomShuffleLCG(_ N: Int) {
59+
var generator = LCRNG(seed: 0)
60+
for _ in 0 ..< N {
61+
numbers.shuffle(using: &generator)
62+
blackHole(numbers.first!)
63+
}
64+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===--- RandomValues.swift -----------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import TestsUtils
14+
15+
//
16+
// Benchmark generating lots of random values. Measures the performance of
17+
// the default random generator and the algorithms for generating integers
18+
// and floating-point values.
19+
//
20+
21+
public let RandomValues = [
22+
BenchmarkInfo(name: "RandomIntegersDef", runFunction: run_RandomIntegersDef, tags: [.api]),
23+
BenchmarkInfo(name: "RandomIntegersLCG", runFunction: run_RandomIntegersLCG, tags: [.api]),
24+
BenchmarkInfo(name: "RandomDoubleDef", runFunction: run_RandomDoubleDef, tags: [.api]),
25+
BenchmarkInfo(name: "RandomDoubleLCG", runFunction: run_RandomDoubleLCG, tags: [.api]),
26+
]
27+
28+
/// A linear congruential PRNG.
29+
struct LCRNG: RandomNumberGenerator {
30+
private var state: UInt64
31+
32+
init(seed: Int) {
33+
state = UInt64(truncatingIfNeeded: seed)
34+
for _ in 0..<10 { _ = next() }
35+
}
36+
37+
mutating func next() -> UInt64 {
38+
state = 2862933555777941757 &* state &+ 3037000493
39+
return state
40+
}
41+
}
42+
43+
@inline(never)
44+
public func run_RandomIntegersDef(_ N: Int) {
45+
for _ in 0 ..< N {
46+
var x = 0
47+
for _ in 0 ..< 100_000 {
48+
x &+= Int.random(in: 0...10_000)
49+
}
50+
blackHole(x)
51+
}
52+
}
53+
54+
@inline(never)
55+
public func run_RandomIntegersLCG(_ N: Int) {
56+
for _ in 0 ..< N {
57+
var x = 0
58+
var generator = LCRNG(seed: 0)
59+
for _ in 0 ..< 100_000 {
60+
x &+= Int.random(in: 0...10_000, using: &generator)
61+
}
62+
CheckResults(x == 498214315)
63+
}
64+
}
65+
66+
@inline(never)
67+
public func run_RandomDoubleDef(_ N: Int) {
68+
for _ in 0 ..< N {
69+
var x = 0.0
70+
for _ in 0 ..< 100_000 {
71+
x += Double.random(in: -1000...1000)
72+
}
73+
blackHole(x)
74+
}
75+
}
76+
77+
@inline(never)
78+
public func run_RandomDoubleLCG(_ N: Int) {
79+
for _ in 0 ..< N {
80+
var x = 0.0
81+
var generator = LCRNG(seed: 0)
82+
for _ in 0 ..< 100_000 {
83+
x += Double.random(in: -1000...1000, using: &generator)
84+
}
85+
blackHole(x)
86+
}
87+
}

benchmark/utils/main.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -110,6 +110,8 @@ import ProtocolDispatch2
110110
import Queue
111111
import RC4
112112
import RGBHistogram
113+
import RandomShuffle
114+
import RandomValues
113115
import RangeAssignment
114116
import RangeIteration
115117
import RangeReplaceableCollectionPlusDefault
@@ -264,6 +266,8 @@ registerBenchmark(QueueGeneric)
264266
registerBenchmark(QueueConcrete)
265267
registerBenchmark(RC4Test)
266268
registerBenchmark(RGBHistogram)
269+
registerBenchmark(RandomShuffle)
270+
registerBenchmark(RandomValues)
267271
registerBenchmark(RangeAssignment)
268272
registerBenchmark(RangeIteration)
269273
registerBenchmark(RangeReplaceableCollectionPlusDefault)

stdlib/public/SwiftShims/LibcShims.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -149,6 +149,10 @@ __swift_uint32_t _stdlib_cxx11_mt19937(void);
149149
SWIFT_RUNTIME_STDLIB_INTERNAL
150150
__swift_uint32_t _stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound);
151151

152+
// Random number for stdlib
153+
SWIFT_RUNTIME_STDLIB_INTERNAL
154+
void _stdlib_random(void *buf, __swift_size_t nbytes);
155+
152156
// Math library functions
153157
static inline SWIFT_ALWAYS_INLINE
154158
float _stdlib_remainderf(float _self, float _other) {

stdlib/public/core/Bool.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -86,6 +86,28 @@ public struct Bool {
8686
public init(_ value: Bool) {
8787
self = value
8888
}
89+
90+
/// Returns a random Boolean value
91+
///
92+
/// - Parameter generator: The random number generator to use when getting a
93+
/// random Boolean.
94+
/// - Returns: A random Boolean value.
95+
@inlinable
96+
public static func random<T: RandomNumberGenerator>(
97+
using generator: inout T
98+
) -> Bool {
99+
return (generator.next() >> 17) & 1 == 0
100+
}
101+
102+
/// Returns a random Boolean value
103+
///
104+
/// - Returns: A random Boolean value.
105+
///
106+
/// This uses the standard library's default random number generator.
107+
@inlinable
108+
public static func random() -> Bool {
109+
return Bool.random(using: &Random.default)
110+
}
89111
}
90112

91113
extension Bool : _ExpressibleByBuiltinBooleanLiteral, ExpressibleByBooleanLiteral {

stdlib/public/core/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# This source file is part of the Swift.org open source project
44
#
5-
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
# Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
# Licensed under Apache License v2.0 with Runtime Library Exception
77
#
88
# See https://swift.org/LICENSE.txt for license information
@@ -97,6 +97,7 @@ set(SWIFTLIB_ESSENTIAL
9797
Policy.swift
9898
PrefixWhile.swift
9999
Print.swift
100+
Random.swift
100101
RandomAccessCollection.swift
101102
Range.swift
102103
RangeReplaceableCollection.swift

stdlib/public/core/Collection.swift

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -803,6 +803,25 @@ public protocol Collection: Sequence where SubSequence: Collection {
803803
/// `endIndex`.
804804
func formIndex(after i: inout Index)
805805

806+
/// Returns a random element of the collection, using the given generator as
807+
/// a source for randomness.
808+
///
809+
/// You use this method to select a random element from a collection when you
810+
/// are using a custom random number generator. For example, call
811+
/// `randomElement(using:)` to select a random element from an array of names.
812+
///
813+
/// let names = ["Zoey", "Chloe", "Amani", "Amaia"]
814+
/// let randomName = names.randomElement(using: &myGenerator)!
815+
/// // randomName == "Amani" (maybe)
816+
///
817+
/// - Parameter generator: The random number generator to use when choosing
818+
/// a random element.
819+
/// - Returns: A random element from the collection. If the collection is
820+
/// empty, the method returns `nil`.
821+
func randomElement<T: RandomNumberGenerator>(
822+
using generator: inout T
823+
) -> Element?
824+
806825
@available(*, deprecated, message: "all index distances are now of type Int")
807826
typealias IndexDistance = Int
808827
}
@@ -1016,6 +1035,54 @@ extension Collection {
10161035
return count
10171036
}
10181037

1038+
/// Returns a random element of the collection, using the given generator as
1039+
/// a source for randomness.
1040+
///
1041+
/// You use this method to select a random element from a collection when you
1042+
/// are using a custom random number generator. For example, call
1043+
/// `randomElement(using:)` to select a random element from an array of names.
1044+
///
1045+
/// let names = ["Zoey", "Chloe", "Amani", "Amaia"]
1046+
/// let randomName = names.randomElement(using: &myGenerator)!
1047+
/// // randomName == "Amani" (maybe)
1048+
///
1049+
/// - Parameter generator: The random number generator to use when choosing
1050+
/// a random element.
1051+
/// - Returns: A random element from the collection. If the collection is
1052+
/// empty, the method returns `nil`.
1053+
@inlinable
1054+
public func randomElement<T: RandomNumberGenerator>(
1055+
using generator: inout T
1056+
) -> Element? {
1057+
guard !isEmpty else { return nil }
1058+
let random = generator.next(upperBound: UInt(count))
1059+
let index = self.index(
1060+
startIndex,
1061+
offsetBy: numericCast(random)
1062+
)
1063+
return self[index]
1064+
}
1065+
1066+
/// Returns a random element of the collection.
1067+
///
1068+
/// For example, call `randomElement()` to select a random element from an
1069+
/// array of names.
1070+
///
1071+
/// let names = ["Zoey", "Chloe", "Amani", "Amaia"]
1072+
/// let randomName = names.randomElement()!
1073+
/// // randomName == "Amani" (perhaps)
1074+
///
1075+
/// This method uses the default random generator, `Random.default`. The call
1076+
/// to `names.randomElement()` above is equivalent to calling
1077+
/// `names.randomElement(using: &Random.default)`.
1078+
///
1079+
/// - Returns: A random element from the collection. If the collection is
1080+
/// empty, the method returns `nil`.
1081+
@inlinable
1082+
public func randomElement() -> Element? {
1083+
return randomElement(using: &Random.default)
1084+
}
1085+
10191086
/// Do not use this method directly; call advanced(by: n) instead.
10201087
@inlinable
10211088
@inline(__always)

0 commit comments

Comments
 (0)