1

I have been looking into potential use cases of UnsafePointer and related UnsafeX in Swift, and am wondering what the use case is i Swift. It sounds like the main use case is performance, but then at the same time types are supposed to offer compiler optimizations and so performance, so I'm not sure when they are actually useful. I would like to know if all things can be refactored to not use them with the same or better performance, or if not, what a specific example with description of the code and perhaps some code or pseudocode that demonstrates how it offers a performance advantage. I would basically like to have a reference of a specific example demoing a performance advantage of unsafe pointers and unsafe stuff.

Some things I've found related to Swift:

However, UnsafePointer is an important API for interoperability and building high performance data structures. - http://atrick.github.io/proposal/voidpointer.html

But typing allows for compiler optimizations. I'm wondering what advantages using the Unsafe features gives you.

Some places you see the use of this is in Metal code, such as here:

// Create buffers used in the shader
guard let uniformBuffer = device.makeBuffer(length: MemoryLayout<Uniforms>.stride) else { throw Error.failedToCreateMetalBuffer(device: device) }
uniformBuffer.label = "me.dehesa.metal.buffers.uniform"
uniformBuffer.contents().bindMemory(to: Uniforms.self, capacity: 1)

// or here
let ptr = uniformsBuffer.contents().assumingMemoryBound(to: Uniforms.self)
ptr.pointee = Uniforms(modelViewProjectionMatrix: modelViewProjectionMatrix, modelViewMatrix: modelViewMatrix, normalMatrix: normalMatrix)

I don't really understand what's going on with the pointers too well yet, but I wanted to ask to see if these use cases offer performance enhancements or if they could be refactored to use a safe version that had similar or even better performance.

Saw it here too:

func setBit(_ index: Int, value: Bool, pointer: UnsafeMutablePointer<UInt8>) {
    let bit: UInt8 = value ? 0xFF : 0
    pointer.pointee ^= (bit ^ pointer.pointee) & (1 << UInt8(index))
  }

More metal:

uniforms = UnsafeMutableRawPointer(uniformBuffer.contents()).bindMemory(to:GUniforms.self, capacity:1)

vertexBuffer = device?.makeBuffer(length: 3 * MemoryLayout<GVertex>.stride * 6, options: .cpuCacheModeWriteCombined)
vertices = UnsafeMutableRawPointer(vertexBuffer!.contents()).bindMemory(to:GVertex.self, capacity:3)

vertexBuffer1 = device?.makeBuffer(length: maxCount * maxCount * MemoryLayout<GVertex>.stride * 4, options: .cpuCacheModeWriteCombined)
vertices1 = UnsafeMutableRawPointer(vertexBuffer1!.contents()).bindMemory(to:GVertex.self, capacity: maxCount * maxCount * 4)

Stuff regarding images:

func mapIndicesRgba(_ imageIndices: Data, size: Size2<Int>) -> Data {
    let palette = self
    var pixelData = Data(count: size.area * 4)
    pixelData.withUnsafeMutableBytes() { (pixels: UnsafeMutablePointer<UInt8>) in
        imageIndices.withUnsafeBytes { (indices: UnsafePointer<UInt8>) in
            var pixel = pixels
            var raw = indices
            for _ in 0..<(size.width * size.height) {
                let colorIndex = raw.pointee
                pixel[0] = palette[colorIndex].red
                pixel[1] = palette[colorIndex].green
                pixel[2] = palette[colorIndex].blue
                pixel[3] = palette[colorIndex].alpha
                pixel += 4
                raw += 1
            }
        }
    }
    return pixelData
}

Stuff regarding input streams:

fileprivate extension InputStream {
    fileprivate func loadData(sizeHint: UInt) throws -> Data {
        let hint = sizeHint == 0 ? BUFFER_SIZE : Int(sizeHint)
        var buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: hint)
        var totalBytesRead = read(buffer, maxLength: hint)

        while hasBytesAvailable {
            let newSize = totalBytesRead * 3 / 2
            // Ehhhh, Swift Foundation's Data doesnt have `increaseLength(by:)` method anymore
            // That is why we have to go the `realloc` way... :(
            buffer = unsafeBitCast(realloc(buffer, MemoryLayout<UInt8>.size * newSize), to: UnsafeMutablePointer<UInt8>.self)
            totalBytesRead += read(buffer.advanced(by: totalBytesRead), maxLength: newSize - totalBytesRead)
        }

        if streamStatus == .error {
            throw streamError!
        }

        // FIXME: Probably should use Data(bytesNoCopy: .. ) instead, but will it deallocate the tail of not used buffer?
        // leak check must be done
        let retVal = Data(bytes: buffer, count: totalBytesRead)
        free(buffer)
        return retVal
    }
}
Lance
  • 75,200
  • 93
  • 289
  • 503
  • I'm working on Swift code which uses the C pcap library to capture packets from the network. Using UnsafeMutablePointer is required to access those C APIs. There are plenty of other examples of C APIs which require UnsafePointers. Every pointer in C can be considered unsafe. They are just not labeled that way ;-) – Darrell Root Jan 29 '20 at 22:15

1 Answers1

2

Swift semantics allows it to make copies of certain data types for safety when reading and potentially writing non-atomic-sized chunks of memory (copy-on-write allocations, etc.). This data copy operation possibly requires a memory allocation, which potentially can cause a lock with unpredictable latency.

An unsafe pointer can be used to pass a reference to a (possibly)mutable array (or block of bytes), or slice thereof, that should not be copied, no matter how (unsafely) accessed or passed around between functions or threads. This potentially reduces the need for the Swift runtime to do as many memory allocations.

I had one prototype iOS application where Swift was spending significant percentages of CPU (and likely the user’s battery life) allocating and copying multi-megabyte-sized slices of regular Swift arrays passed to functions at a very high rate, some mutating, some not mutating them (for near-real-time RF DSP analysis). A large GPU texture, sub-texture-slice accessed each frame refresh, possibly could have similar issues. Switching to unsafe pointers referencing C allocations of memory stopped this performance/battery waste in my vanilla Swift prototype (the extraneous allocate and copy operations disappeared from the performance profiling).

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • Note that Apple in a 2018 WWDC talk specifically recommended not using Swift and thus Swift data types inside real-time Audio Unit callbacks for similar performance reasons. – hotpaw2 Mar 18 '19 at 00:16