Random Numbers In Swift

This post will dive into generating random numbers in Swift on iOS and macOS, using Instruments to profile performance. If you are not familiar with Instruments (part of Xcode) check out this Instruments Overview.

Generating Random Numbers In Swift

Swift provides a convenient interface for obtaining random numbers:

// Random integer in the range [0, 10)

Random Elements In Swift

Swift also provides an interface for obtaining random elements of collections:

// Random element in the collection [0, 2]
[0, 1, 2].randomElement()

How Swift Generates Random Numbers

Behind the scenes, certain types and collections adhere to the RandomNumberGenerator protocol in the Swift standard library. On iOS and macOS the implementation of RandomNumberGenerator relies on calling the _stdlib_random function defined in LibcShims.cpp:

void swift::_stdlib_random(void *buf, __swift_size_t nbytes) {
  arc4random_buf(buf, nbytes);

The function arc4random_buf(void *buf, size_t nbytes) generates nbytes of randomness and writes the random bytes to buffer buf. Knowing the trace and underlying root function we can look at the performance characteristics for random number generation.

Performance of Random Numbers in Swift

While looking into the performance of IndexSet.union, a performance trace from Instruments contained an unexpected result. The code profiled in this trace repeatedly inserted numbers into an IndexSet a random number of times.

Time Weight Symbol
6.00ms 20.0% swift_retain(swift::HeapObject*)
4.00ms 13.3% static UInt._random(using:)
4.00ms 13.3%     static Int._random(using:)
4.00ms 13.3%         protocol witness for RandomNumberGenerator._fill(bytes:)
4.00ms 13.3%             arc4random_buf
2.00ms 6.6% IndexSet.insert(_:)
2.00ms 6.6%     NSMutableIndexSet.add(_:)
2.00ms 6.6% swift_release(swift::HeapObject*)

The odd part is the amount of compute time spent on arc4random_buf versus NSMutableIndexSet.add(_:). Naively, we might assume adding into an index set takes more compute than generating a random numbers.

Upon further profiling, the results indicate arc4random_buf is slower than arc4random and arc4random_uniform. Using this information we can construct more performant alternatives to the existing Swift interfaces:

// Faster implementation to get a random index in a collection
let index = arc4random_uniform(collection.count)

// Faster implementation to get a 64-bit random number
let value1: UInt32 = arc4random()
let value2: UInt32 = arc4random()
let random = (UInt64(value1) << 32 | UInt64(value2))

Fast Random Numbers in Swift

On the machine used for profiling, combining two 32-bit numbers was over 500% faster than using arc4random_buf.

Note: if generating random numbers securely is a relevant concern to your application consider using SecRandomCopyBytes.

If your application relies on random numbers, especially for graphs or game related computation, consider looking into the different performance characteristics of the available randomness functions on the platforms your application supports. You may be able to achieve a meaningful performance improvement by optimizing random number generation.