Skip to content

Instantly share code, notes, and snippets.

@Azoy
Created April 25, 2018 01:50
Show Gist options
  • Save Azoy/15f971130fbf5ee9cc0a91552d51ffe6 to your computer and use it in GitHub Desktop.
Save Azoy/15f971130fbf5ee9cc0a91552d51ffe6 to your computer and use it in GitHub Desktop.
Add `fillBuffer(_:)` to `RandomNumberGenerator` (WIP)

Add fillBuffer(_:) to RandomNumberGenerator

Introduction

This proposal aims to add a new method, fillBuffer(_:), to RandomNumberGenerator, and aims to move an already defaulted method into a requirement.

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

Currently if someone wants fill a buffer with random bytes, they have to loop through the buffer calling next() multiple times to get many UInt8s. For small buffers this isn't a real problem, but for very large buffers that need to be filled with random bytes, this can be an issue. Take, for example, Random.default. This is a stateless generator that may use a system call to fill a number with quality bits. Potentially filling a buffer with this generator could be a real burden to performance because of so many system calls.

Proposed solution

I propose adding fillBuffer(_:) to RandomNumberGenerator and adding next<T : FixedWidthInteger & UnsignedInteger>() -> T as a requirement.

Filling a buffer example:

// Fill an arbitray buffer with 100 random bytes
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 100, alignment: 1)
Random.default.fillBuffer(buffer)

// Useful in extensions like Data
extension Data {
  static func random<T: RandomNumberGenerator>(
    byteCount: Int,
    using generator: inout T
  ) -> Data {
    let buffer = UnsafeMutableRawBufferPointer.allocate(
      byteCount: byteCount,
      alignment: 1
    )
    generator.fillBuffer(buffer)
    return Data(bytes: buffer.baseAddress!, count: byteCount)
  }
  
  static func random(byteCount: Int) -> Data {
    return Data.random(byteCount: byteCount, using: &Random.default)
  }
}

let data = Data.random(byteCount: 100)

This operation on Data using the default RNG now does 1 or 2 system calls vs the 100 through looping. Many other custom RNG implementations are also able to optimize away filling buffers without having to loop through calling next() several times.

Detailed design

// Resulting protocol
public protocol RandomNumberGenerator {
  func next() -> UInt64 // Already requirement
  func next<T: FixedWidthInteger & UnsignedInteger>() -> T // Moving to requirement
  func fillBuffer(_ buffer: UnsafeMutableRawBufferPointer)
}

extension RandomNumberGenerator {
  public func next<T: FixedWidthInteger & UnsignedInteger>() -> T {
    // Already a default method on `RandomNumberGenerator`
  }
  
  public func fillBuffer(_ buffer: UnsafeMutableRawBufferPointer) {
    // Add default implementation for this requirement
  }
}

public struct Random : RandomNumberGenerator {
  public func fillBuffer(_ buffer: UnsafeMutableRawBufferPointer) {
    // Use custom implementation here to prevent looping through `next()`
  }
}

Source compatibility

Relative to the Swift 3 evolution process, the source compatibility requirements for Swift 4 are much more stringent: we should only break source compatibility if the Swift 3 constructs were actively harmful in some way, the volume of affected Swift 3 code is relatively small, and we can provide source compatibility (in Swift 3 compatibility mode) and migration.

Will existing correct Swift 3 or Swift 4 applications stop compiling due to this change? Will applications still compile but produce different behavior than they used to? If "yes" to either of these, is it possible for the Swift 4 compiler to accept the old syntax in its Swift 3 compatibility mode? Is it possible to automatically migrate from the old syntax to the new syntax? Can Swift applications be written in a common subset that works both with Swift 3 and Swift 4 to aid in migration?

Effect on ABI stability

Does the proposal change the ABI of existing language features? The ABI comprises all aspects of the code generation model and interaction with the Swift runtime, including such things as calling conventions, the layout of data types, and the behavior of dynamic features in the language (reflection, dynamic dispatch, dynamic casting via as?, etc.). Purely syntactic changes rarely change existing ABI. Additive features may extend the ABI but, unless they extend some fundamental runtime behavior (such as the aforementioned dynamic features), they won't change the existing ABI.

Features that don't change the existing ABI are considered out of scope for Swift 4 stage 1. However, additive features that would reshape the standard library in a way that changes its ABI, such as where clauses for associated types, can be in scope. If this proposal could be used to improve the standard library in ways that would affect its ABI, describe them here.

Effect on API resilience

API resilience describes the changes one can make to a public API without breaking its ABI. Does this proposal introduce features that would become part of a public API? If so, what kinds of changes can be made without breaking ABI? Can this feature be added/removed without breaking ABI? For more information about the resilience model, see the library evolution document in the Swift repository.

Alternatives considered

Describe alternative approaches to addressing the same problem, and why you chose this approach instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment