Why is Int.random() slower than arc4random_uniform()?

依然范特西╮ 提交于 2021-02-07 14:16:55

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!