【问题标题】:Reason why Unsafe Pointers are used in Swift, especially in MetalSwift 中使用不安全指针的原因,尤其是在 Metal 中
【发布时间】:2019-03-17 22:23:22
【问题描述】:

我一直在研究 Swift 中 UnsafePointer 和相关 UnsafeX 的潜在用例,我想知道我 Swift 的用例是什么。听起来主要用例是性能,但同时类型应该提供编译器优化和性能,所以我不确定它们什么时候真正有用。我想知道是否可以重构所有东西以不以相同或更好的性能使用它们,或者如果不是,那么带有代码描述的具体示例以及可能演示它如何提供性能优势的一些代码或伪代码。我基本上想参考一个特定示例来演示不安全指针和不安全内容的性能优势。

我发现的一些与 Swift 相关的东西:

不过,UnsafePointer 是用于互操作性和构建高性能数据结构的重要 API。 - http://atrick.github.io/proposal/voidpointer.html

但是打字允许编译器优化。我想知道使用 Unsafe 功能会给您带来什么好处。

你在Metal代码中看到使用this的一些地方,比如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)

我还不太了解指针发生了什么,但我想看看这些用例是否提供了性能增强,或者是否可以重构它们以使用具有相似甚至更好的安全版本性能。

也看到here

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 关于图片:

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 关于输入流:

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

【问题讨论】:

  • 我正在编写使用 C pcap 库从网络捕获数据包的 Swift 代码。访问这些 C API 需要使用 UnsafeMutablePointer。还有很多其他需要 UnsafePointers 的 C API 示例。 C 中的每个指针都可以被认为是不安全的。他们只是没有那样标记;-)

标签: swift performance unsafe-pointers


【解决方案1】:

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

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

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

【讨论】:

  • 请注意,出于类似的性能原因,Apple 在 2018 年 WWDC 演讲中特别建议不要在实时音频单元回调中使用 Swift 和 Swift 数据类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-26
  • 1970-01-01
  • 2017-01-04
相关资源
最近更新 更多