Reason why Unsafe Pointers are used in Swift, especially in Metal

时间:2019-03-17 22:23:22

标签: swift performance unsafe-pointers

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

1 个答案:

答案 0 :(得分:2)

Swift语义允许它在读取和可能写入非原子大小的内存块(写时复制分配等)时为安全起见而复制某些数据类型。此数据复制操作可能需要分配内存,这可能会导致锁定具有不可预知的延迟。

不安全的指针可用于传递对(可能)可变数组(或字节块)或其片段的引用,无论在函数或函数之间如何(不安全地)访问或传递它,都不应复制线程。这样可能减少了Swift运行时执行尽可能多的内存分配的需求。

我有一个原型iOS应用程序,其中Swift花费了大量的CPU资源(可能还消耗了用户的电池寿命)来分配和复制以很高的速率传递给函数的数兆字节大小的常规Swift数组切片,其中有些变异,一些不改变它们(用于近实时RF DSP分析)。较大的GPU纹理,子纹理切片访问每个帧都会刷新,可能会出现类似的问题。切换到引用内存C分配的不安全指针,可以停止我的原始Swift原型中的这种性能/电池浪费(无关的分配和复制操作从性能配置文件中消失了。)