【问题标题】:Why is Int.random() slower than arc4random_uniform()?为什么 Int.random() 比 arc4random_uniform() 慢?
【发布时间】:2019-07-24 05:01:31
【问题描述】:

我使用 Int.random() 方法和 arc4random_uniform() 进行数字生成速度测试。
两个测试都在 macOS 控制台中运行,构建配置设置为发布。 以下是我用于测试的代码。

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)
}

我得到的时间是
0.029475092887878418(用于 arc4random_uniform(10))
0.20298802852630615(对于 Int.random(in: 0..

为什么 Int.random() 这么慢?
有没有办法优化它?
swift中有没有更快的随机数生成方法?

【问题讨论】:

  • 您使用了哪些优化器设置?
  • 查看分析器中的代码,似乎Int.random(in:) 在内部使用arc4random_buf,这似乎比arc4random_uniform 使用的arc4random 慢很多。
  • 公平地说,arc4random_uniform() 产生一个UInt32,因此它应该与UInt32.random(in:) 进行比较
  • @ielyamani:我试过了,没有区别。 Int.random() 和 UInt32.random() 都计算一个 64 位随机数,然后将结果“截断”到所需范围。
  • 永远不要使用CFAbsoluteTimeGetCurrent:它是挂钟,受日历中的闰秒影响。而是使用单调时钟:mach_absolute_time()ProcessInfo.processInfo.systemUptimeDispatchTime.now()CACurrentMediaTime()

标签: swift random


【解决方案1】:

更新

This 区间内随机数生成器的实现已合并到标准库中,性能应该比以前更好:

// 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
}

有关详细信息,请参阅下面的答案。

回答

第二个问题的答案:

“在swift中有没有更快的随机数生成方法?”

我有 previously used Xoshiro 伪随机数生成器,速度非常快。

这里是用于基准测试的代码:

  • 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()
}
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 速度很快,但没有通过所有随机性测试。如果担心安全问题,您可以使用Wyhash

Daniel Lemirethis 论文的作者)刚刚给我发了 Wyhash 的Swift implementation

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
    }
}

可以这样使用:

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()
}

这里是基准测试结果,在终端中编译的代码经过优化 (-O):

arc4random_uniform()  : 0.034s
UInt32.random(in:)    : 0.243s
WyHash64              : 0.002s
Xoshiro               : 0.001s

你可以找到更多随机数生成器here

【讨论】:

  • 在从SystemRandomNumberGenerator 切换到 Xoshiro 时,请慎重考虑。前者尽可能是一种加密安全的 PRNG。 Xoshiro 不是加密安全的 PRNG。在突然变得非常重要之前,这种事情并不重要。 CSPRNG 的安全性具有性能成本,因此任何体面的非 CSPRNG 都应该更快。
  • 请注意,RandomNumberGenerator 必须实现public mutating func next() -&gt; UInt64 - 然后您可以调用Int.random(in: 0..&lt;10, using: &amp;x) 而不是模运算(存在“模偏差”问题)。
  • 顺便说一句,在此处观察到 RNG 在没有所需方法的情况下编译:forums.swift.org/t/…
  • @ielyamani 我不认为 Big Crush 是对 CSPRNG(加密安全 PRNG)的充分测试。 Big Crush 只是对统计随机性的测试。这大大低于 CSPRNG 的承诺。例如,要求确定 CSPRNG 先前的输出是不切实际的(即超过多项式时间),即使您知道其当前的全部内部状态。通过基本统计测试是任何 PRNG 的赌注,但没有统计测试可以证明 CSPRNG。
  • 维基百科的 CSPRNG 文章并不是对 IMO 主题的精彩介绍,但它确实包含了一个很好的例子,说明了一个很好的 PRNG 完全被密码破解的例子。生成一个种子并使用它来选择 pi 的一个数字。对于每次迭代,输出下一个数字。这对于生成将通过统计测试的随机序列非常好,但是如果您知道 PRNG 当前正在计算哪个数字(即 PRNG 的内部状态),那么确定所有先前输出的数字就很简单了。 en.wikipedia.org/wiki/…
猜你喜欢
  • 2011-03-23
  • 1970-01-01
  • 2019-12-18
  • 2015-10-04
  • 2012-09-16
  • 2016-10-06
  • 2020-11-27
  • 2011-10-19
  • 2020-12-24
相关资源
最近更新 更多