Причина, по которой небезопасные указатели используются в Swift, особенно в металле - PullRequest
1 голос
/ 18 марта 2019

Я изучал возможные варианты использования UnsafePointer и связанных с ним UnsafeX в Swift, и мне интересно, что это за вариант использования i Swift. Похоже, что основным вариантом использования является производительность, но в то же время типы должны обеспечивать оптимизацию компилятора и производительность, поэтому я не уверен, когда они действительно полезны. Я хотел бы знать, могут ли все вещи быть реорганизованы, чтобы не использовать их с одинаковой или лучшей производительностью, или, если нет, какой конкретный пример с описанием кода и, возможно, некоторого кода или псевдокода, который демонстрирует, как он предлагает преимущество в производительности. Мне бы хотелось получить ссылку на конкретный пример, демонстрирующий преимущество в производительности из-за небезопасных указателей и небезопасных вещей.

Некоторые вещи, которые я нашел, связанные со Swift:

Однако UnsafePointer является важным API для взаимодействия и построения высокопроизводительных структур данных. - http://atrick.github.io/proposal/voidpointer.html

Но типизация позволяет оптимизировать компилятор. Мне интересно, какие преимущества дает использование небезопасных функций.

В некоторых местах вы видите использование этого в коде Metal, например здесь :

// 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)

Я пока не очень хорошо понимаю, что происходит с указателями, но я хотел спросить, предлагают ли эти варианты использования повышение производительности или их можно было бы реорганизовать, чтобы использовать безопасную версию, имеющую аналогичную или даже лучшую производительность.

увидел здесь тоже:

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

Больше металла :

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)

Материал относительно изображений:

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
}

Материал относительно входных потоков:

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 Ответ

2 голосов
/ 18 марта 2019

Семантика Swift позволяет делать копии определенных типов данных для безопасности при чтении и потенциальной записи фрагментов памяти не атомарного размера (выделения при записи и т. Д.).Эта операция копирования данных, возможно, требует выделения памяти, что потенциально может вызвать блокировку с непредсказуемой задержкой.

Небезопасный указатель может использоваться для передачи ссылки на (возможно) изменяемый массив (или блок байтов),или его фрагмент, который не следует копировать, независимо от того, как (небезопасно) осуществляется доступ или передача между функциями или потоками.Это потенциально уменьшает потребность во времени выполнения Swift для выполнения стольких выделений памяти.

У меня было одно прототипное приложение для iOS, в котором Swift тратил значительный процент ресурсов ЦП (и, вероятно, времени работы от батареи пользователя), выделяя и копируя несколько мегабайтсрезы регулярных массивов Swift передаются функциям с очень высокой скоростью, некоторые мутируют, некоторые не мутируют (для анализа RF DSP почти в реальном времени).Большая текстура GPU, суб-текстура-срез, доступ к которой происходит при каждом обновлении кадра, возможно, может иметь аналогичные проблемы.Переключение на небезопасные указатели, ссылающиеся на выделение памяти в C, остановило эту потерю производительности / батареи в моем простом прототипе Swift (посторонние операции выделения и копирования исчезли из профилирования производительности).

...