【问题标题】:Fastest way to save structs iOS / Swift保存结构的最快方法 iOS / Swift
【发布时间】:2020-07-21 16:10:12
【问题描述】:

我有类似的结构

struct RGBA: Codable {
        
   var r: UInt8
   var g: UInt8
   var b: UInt8
   var a: UInt8 
}

我想保存大量这种结构 (>1_000_000)

解码

guard let history = try? JSONDecoder().decode(HistoryRGBA.self, from: data) else { return }

编码

guard let jsonData = try? encoder.encode(dataForSave) else { return false }

如何提高编码/解码时间和 RAM 内存量?

【问题讨论】:

  • 你需要这个是 JSON 吗?有更有效的方法来编码 32 位字数组。另外,当您说“保存”时,您是如何将这些写入磁盘的?整个数组是否会发生变化,或者您可以使用随机访问更新内容吗?或者数据一旦写入就不可改变?根据数据的性质及其访问权限,有许多可用的性能增强功能。
  • @napier 我需要任何方法来保存和加载大量结构。保存格式不重要
  • 数据保存到磁盘。数据可以被覆盖

标签: ios swift decode codable encodable


【解决方案1】:

JSONEncoder/Decoder 的性能表现……不是很好。 ZippyJSON 是一个插入式替代品,据说比 Foundation 的实现快 4 倍,如果你想要更好的性能和更低的内存使用,你可能想要谷歌搜索某种流式 JSON 解码器库。

但是,您在 cmets 中说您不需要 JSON 格式。太好了,因为我们可以更有效地将数据存储为原始字节数组,而不是 JSON 等基于文本的格式:

extension RGBA {
    static let size = 4 // the size of a (packed) RGBA structure
}

// encoding
var data = Data(count: history.rgba.count * RGBA.size)
for i in 0..<history.rgba.count {
    let rgba = history.rgba[i]
    data[i*RGBA.size] = rgba.r
    data[i*RGBA.size+1] = rgba.g
    data[i*RGBA.size+2] = rgba.b
    data[i*RGBA.size+3] = rgba.a
}


// decoding
guard data.count % RGBA.size == 0 else {
    // data is incomplete, handle error
    return
}
let rgbaCount = data.count / RGBA.size
var result = [RGBA]()
result.reserveCapacity(rgbaCount)
for i in 0..<rgbaCount {
    result.append(RGBA(r: data[i*RGBA.size],
                       g: data[i*RGBA.size+1],
                       b: data[i*RGBA.size+2],
                       a: data[i*RGBA.size+3]))
}

这已经比在我的机器上使用 JSONEncoder 快了大约 50 倍(约 100 毫秒而不是约 5 秒)。

您可以通过绕过 Swift 的一些安全检查和内存管理并下拉到原始指针来获得更快的速度:

// encoding
let byteCount = history.rgba.count * RGBA.size
let rawBuf = malloc(byteCount)!
let buf = rawBuf.bindMemory(to: UInt8.self, capacity: byteCount)

for i in 0..<history.rgba.count {
    let rgba = history.rgba[i]
    buf[i*RGBA.size] = rgba.r
    buf[i*RGBA.size+1] = rgba.g
    buf[i*RGBA.size+2] = rgba.b
    buf[i*RGBA.size+3] = rgba.a
}
let data = Data(bytesNoCopy: rawBuf, count: byteCount, deallocator: .free)


// decoding
guard data.count % RGBA.size == 0 else {
    // data is incomplete, handle error
    return
}
let result: [RGBA] = data.withUnsafeBytes { rawBuf in
    let buf = rawBuf.bindMemory(to: UInt8.self)
    let rgbaCount = buf.count / RGBA.size
    return [RGBA](unsafeUninitializedCapacity: rgbaCount) { resultBuf, initializedCount in
        for i in 0..<rgbaCount {
            resultBuf[i] = RGBA(r: data[i*RGBA.size],
                                g: data[i*RGBA.size+1],
                                b: data[i*RGBA.size+2],
                                a: data[i*RGBA.size+3])
        }
    }
}

我的机器上的基准测试结果(我没有测试 ZippyJSON):

JSON:
Encode: 4967.0ms; 32280478 bytes
Decode: 5673.0ms

Data:
Encode: 96.0ms; 4000000 bytes
Decode: 19.0ms

Pointers:
Encode: 1.0ms; 4000000 bytes
Decode: 18.0ms

您可以通过直接将数组从内存写入磁盘而不对其进行序列化来获得更快的速度,尽管我也没有测试过。当然,当您测试性能时,请确保您在发布模式下进行测试。

【讨论】:

    【解决方案2】:

    考虑到您的所有属性都是UInt8(字节),您可以使您的结构符合ContiguousBytes 并保存其原始字节:

    struct RGBA {
       let r, g, b, a: UInt8
    }
    

    extension RGBA: ContiguousBytes {
        func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
            try Swift.withUnsafeBytes(of: self) { try body($0) }
        }
    }
    

    extension ContiguousBytes {
        init<T: ContiguousBytes>(_ bytes: T) {
            self = bytes.withUnsafeBytes { $0.load(as: Self.self) }
        }
    }
    

    extension RGBA: ExpressibleByArrayLiteral {
        typealias ArrayLiteralElement = UInt8
        init(arrayLiteral elements: UInt8...) {
            self.init(elements)
        }
    }
    

    extension Array {
        var bytes: [UInt8] { withUnsafeBytes { .init($0) } }
        var data: Data { withUnsafeBytes { .init($0) } }
    }
    

    extension ContiguousBytes {
        var bytes: [UInt8] { withUnsafeBytes { .init($0) } }
        var data: Data { withUnsafeBytes { .init($0) } }
    }
    

    extension ContiguousBytes {
        func object<T>() -> T { withUnsafeBytes { $0.load(as: T.self) } }
        func objects<T>() -> [T] { withUnsafeBytes { .init($0.bindMemory(to: T.self)) } }
    }
    

    extension ContiguousBytes {
        var rgba: RGBA { object() }
        var rgbaCollection: [RGBA] { objects() }
    }
    

    extension UIColor {
        convenience init<T: Collection>(_ bytes: T) where T.Index == Int, T.Element == UInt8 {
            self.init(red:   CGFloat(bytes[0])/255,
                      green: CGFloat(bytes[1])/255,
                      blue:  CGFloat(bytes[2])/255,
                      alpha: CGFloat(bytes[3])/255)
        }
    }
    

    extension RGBA {
        var color: UIColor { .init(bytes) }
    }
    

    let red: RGBA = [255, 0, 0, 255]
    let green: RGBA = [0, 255, 0, 255]
    let blue: RGBA = [0, 0, 255, 255]
    
    let redBytes = red.bytes            // [255, 0, 0, 255]
    let redData = red.data              // 4 bytes
    let rgbaFromBytes = redBytes.rgba    // RGBA
    let rgbaFromData = redData.rgba      // RGBA
    let colorFromRGBA = red.color       // r 1.0 g 0.0 b 0.0 a 1.0
    let rgba: RGBA = [255,255,0,255]    // RGBA yellow
    let yellow = rgba.color             // r 1.0 g 1.0 b 0.0 a 1.0
    
    let colors = [red, green, blue]      // [{r 255, g 0, b 0, a 255}, {r 0, g 255, b 0, a 255}, {r 0, g 0, b 255, a 255}]
    let colorsData = colors.data          // 12 bytes
    let colorsFromData = colorsData.rgbaCollection // [{r 255, g 0, b 0, a 255}, {r 0, g 255, b 0, a 255}, {r 0, g 0, b 255, a 255}]
    

    编辑/更新:

    struct LayerRGBA {
        var canvas: [[RGBA]]
    }
    

    extension LayerRGBA {
        var data: Data { canvas.data }
        init(_ data: Data) { canvas = data.objects() }
    }
    

    struct AnimationRGBA {
        var layers: [LayerRGBA]
    }
    

    extension AnimationRGBA {
        var data: Data { layers.data }
        init(_ data: Data) {
            layers = data.objects()
        }
    }
    

    struct HistoryRGBA {
        var layers: [LayerRGBA] = []
        var animations: [AnimationRGBA] = []
    }
    

    extension HistoryRGBA {
        var data: Data {
            let layersData = layers.data
            return layersData.count.data + layersData + animations.data
        }
        init(data: Data)  {
            let index = Int(Data(data.prefix(8))).advanced(by: 8)
            self.init(layers: data.subdata(in: 8..<index).objects(),
                      animations: data.subdata(in: index..<data.endIndex).objects())
        }
    }
    

    extension Numeric {
        var data: Data {
            var bytes = self
            return .init(bytes: &bytes, count: MemoryLayout<Self>.size)
        }
    }
    

    extension Numeric {
        init<D: DataProtocol>(_ data: D) {
            var value: Self = .zero
            let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
            self = value
        }
    }
    

    游乐场测试:

    let layer1: LayerRGBA = .init(canvas: [colors,[red],[green, blue]])
    let layer2: LayerRGBA = .init(canvas: [[red],[green, rgba]])
    let loaded: LayerRGBA = .init(layer1.data)
    loaded.canvas[0]
    loaded.canvas[1]
    loaded.canvas[2]
    
    let animationRGBA: AnimationRGBA = .init(layers: [layer1,layer2])
    let loadedAnimation: AnimationRGBA = .init(animationRGBA.data)
    loadedAnimation.layers.count // 2
    loadedAnimation.layers[0].canvas[0]
    loadedAnimation.layers[0].canvas[1]
    loadedAnimation.layers[0].canvas[2]
    loadedAnimation.layers[1].canvas[0]
    loadedAnimation.layers[1].canvas[1]
    
    let hRGBA: HistoryRGBA = .init(layers: [loaded], animations: [animationRGBA])
    let loadedHistory: HistoryRGBA = .init(data: hRGBA.data)
    loadedHistory.layers[0].canvas[0]
    loadedHistory.layers[0].canvas[1]
    loadedHistory.layers[0].canvas[2]
    
    loadedHistory.animations[0].layers[0].canvas[0]
    loadedHistory.animations[0].layers[0].canvas[1]
    loadedHistory.animations[0].layers[0].canvas[2]
    loadedHistory.animations[0].layers[1].canvas[0]
    loadedHistory.animations[0].layers[1].canvas[1]
    

    【讨论】:

    • 如果我有struct LayerRGBA: Codable { var canvas: [[RGBA]] } struct AnimationRGBA: Codable, LayersDrawable { var layers: [LayerRGBA] } struct HistoryRGBA: Codable, LayersDrawable { var layers: [LayerRGBA] = [], var animations: [AnimationRGBA] = [] } 我如何使用你的方法编码到数据并返回?
    猜你喜欢
    • 1970-01-01
    • 2014-03-12
    • 1970-01-01
    • 2013-02-08
    • 1970-01-01
    • 2012-12-18
    • 1970-01-01
    • 1970-01-01
    • 2015-12-19
    相关资源
    最近更新 更多