问题
I have used Int.random() method and arc4random_uniform() for number generation speed tests.
Both tests were run in macOS console with build configuration set to release.
Below are codes which I have used for testing.
public func randomGen1() {
let n = 1_000_000
let startTime = CFAbsoluteTimeGetCurrent()
for i in 0..<n {
_ = arc4random_uniform(10)
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(timeElapsed)
}
public func randomGen2() {
let n = 1_000_000
let startTime = CFAbsoluteTimeGetCurrent()
for i in 0..<n {
_ = Int.random(in: 0..<10)
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(timeElapsed)
}
The times I got are
0.029475092887878418 (for arc4random_uniform(10))
0.20298802852630615 (for Int.random(in: 0..<10))
Why is Int.random() so much slower?
Is there a way to optimise it?
Are there any faster methods for random number generation in swift?
回答1:
Update
This implementation of a random number generator in an interval has been merged into the standard library and should perform better than before:
// s = upperBound; r1, r2 = random numbers from generator
func bounded(s: UInt64, r1:UInt64, r2: UInt64) -> UInt64 {
// r1 would come from invoking generator's next()
var m = r1.multipliedFullWidth(by: s)
if m.low < s {
// let t = (0 &- s) % s // Lemire's original form
var t = 0 &- s // O'Neill's modulo optimization
if t >= s {
t &-= s
if t >= s {
t %= s
}
}
while m.low < t {
// r2 would come from invoking generator's next()
m = r2.multipliedFullWidth(by: s)
}
}
return m.high
}
See the answer below for more details.
Answer
An answer to your second question :
"Are there any faster methods for random number generation in swift?"
I've previously used the Xoshiro Pseudo-Random Number Generator which is pretty fast.
Here the code used for benchmarking :
- randomGen1
import Foundation
public func randomGen1() {
let n = 1_000_000
var sum: UInt32 = 0
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<n {
sum = sum &+ arc4random_uniform(10)
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(sum, timeElapsed)
}
do {
randomGen1()
}
- randomGen2
public func randomGen2() {
let n = 1_000_000
var sum: UInt32 = 0
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<n {
sum = sum &+ UInt32.random(in: 0..<10)
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(sum, timeElapsed)
}
do {
randomGen2()
}
- Xoshiro random number generator from this library:
struct Xoshiro: RandomNumberGenerator {
public typealias StateType = (UInt32, UInt32, UInt32, UInt32)
private var state: StateType
public init(seed: StateType) {
self.state = seed
}
public mutating func next() -> Int {
let x = state.1 &* 5
let result = ((x &<< 7) | (x &>> 25)) &* 9
let t = state.1 &<< 9
state.2 ^= state.0
state.3 ^= state.1
state.1 ^= state.2
state.0 ^= state.3
state.2 ^= t
state.3 = (state.3 &<< 21) | (state.3 &>> 11)
return Int(result)
}
}
var x = Xoshiro(seed: (UInt32.random(in: 0..<10), //Other upper limits could be used to increase randomness
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10)))
public func randomGen3() {
let n = 1_000_000
var sum: UInt32 = 0
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0..<n {
sum = sum &+ UInt32(abs(x.next()) % 10)
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(sum, timeElapsed)
}
do {
randomGen3()
}
Xoshiro is fast but does not pass all randomness tests. If security is of concern then you could use Wyhash.
Daniel Lemire (the author of this paper) has kindly just sent me a Swift implementation of Wyhash:
class WyhashGenerator {
var seed : UInt64
let multiplier1 : UInt64 = 0xa3b195354a39b70d
let multiplier2 : UInt64 = 0x1b03738712fad5c9
let increment : UInt64 = 0x60bee2bee120fc15
init(userSeed : UInt64) {
seed = userSeed;
}
func random() -> UInt64 {
seed &+= increment
let fullmult1 = seed.multipliedFullWidth(by: multiplier1)
let m1 = fullmult1.high ^ fullmult1.low;
let fullmult2 = m1.multipliedFullWidth(by: multiplier2)
let m2 = fullmult2.high ^ fullmult2.low;
return m2
}
}
It can be used like so:
public func randomGen4() {
let n = 1_000_000
var sum: UInt64 = 0
let startTime = CFAbsoluteTimeGetCurrent()
let gen = WyhashGenerator(userSeed: 0)
for _ in 0..<n {
sum = sum &+ gen.random() % 10
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print(sum, timeElapsed)
}
do {
randomGen4()
}
And here are the benchmark results, with the code compiled in the terminal with optimizations (-O
) :
arc4random_uniform() : 0.034s
UInt32.random(in:) : 0.243s
WyHash64 : 0.002s
Xoshiro : 0.001s
You can find more random number generators here.
来源:https://stackoverflow.com/questions/55872415/why-is-int-random-slower-than-arc4random-uniform