Skip to content

Commit

Permalink
Refactoring, optimize keyed array decode.
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaRU committed Jan 31, 2024
1 parent 3ecc509 commit 11bb1ee
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 91 deletions.
88 changes: 66 additions & 22 deletions Sources/CDRCodable/Decoder/CDRDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,69 +55,113 @@ final class _CDRDecoder {
}

extension _CDRDecoder: Decoder {
fileprivate func assertCanCreateContainer() {
precondition(self.container == nil)
}

func container<Key>(keyedBy type: Key.Type) -> KeyedDecodingContainer<Key> where Key : CodingKey {
assertCanCreateContainer()
precondition(self.container == nil)

let container = KeyedContainer<Key>(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
self.container = container

return KeyedDecodingContainer(container)
}

func unkeyedContainer() -> UnkeyedDecodingContainer {
assertCanCreateContainer()
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
precondition(self.container == nil)

let container = UnkeyedContainer(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
self.container = container

return container
}

func singleValueContainer() -> SingleValueDecodingContainer {
assertCanCreateContainer()
precondition(self.container == nil)

let container = SingleValueContainer(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
self.container = container

return container
}
}

protocol _CDRDecodingContainer: AnyObject {
protocol _CDRDecodingContainer {
var codingPath: [CodingKey] { get set }
var userInfo: [CodingUserInfoKey : Any] { get }
var dataStore: _CDRDecoder.DataStore { get }
}

extension _CDRDecodingContainer {
func readByte() throws -> UInt8 {
return try read(1).first!
@inline(__always)
func align(to aligment: Int) {
let offset = self.dataStore.index % aligment
if offset != 0 {
self.dataStore.index = self.dataStore.index.advanced(by: aligment - offset)
}
}

func read(_ length: Int) throws -> Data {

@inline(__always)
func checkDataEnd(_ length: Int) throws {
let nextIndex = self.dataStore.index.advanced(by: length)
guard nextIndex <= self.dataStore.data.endIndex else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Unexpected end of data")
throw DecodingError.dataCorrupted(context)
}
defer { self.dataStore.index = nextIndex }

return self.dataStore.data.subdata(in: self.dataStore.index..<nextIndex)
}

func read<T>(_ type: T.Type) throws -> T where T : FixedWidthInteger {

@inline(__always)
func readCheckBlockCount(of size: Int) throws -> Int {
let length = Int(try read(UInt32.self))
try checkDataEnd(length * size)
return length
}

@inline(__always)
func read<T>(_ type: T.Type) throws -> T where T : Numeric {
let aligment = MemoryLayout<T>.alignment
let offset = self.dataStore.index % aligment
if offset != 0 {
self.dataStore.index = self.dataStore.index.advanced(by: aligment - offset)
}
let stride = MemoryLayout<T>.stride
let bytes = [UInt8](try read(stride))
return T(bytes: bytes)
try checkDataEnd(stride)
defer {
dataStore.index = dataStore.index.advanced(by: stride)
}
return dataStore.data.withUnsafeBytes{ $0.load(fromByteOffset: dataStore.index, as: T.self) }
}

@inline(__always)
func readArray<T>(_ type: T.Type) throws -> [T] where T: Numeric {
let size = MemoryLayout<T>.size
let count = try readCheckBlockCount(of: size)
defer {
dataStore.index = dataStore.index.advanced(by: count * size)
}
return Array<T>.init(unsafeUninitializedCapacity: count) {
dataStore.data.copyBytes(to: $0, from: dataStore.index...)
$1 = count
}
}

@inline(__always)
func readString() throws -> String {
let length = try readCheckBlockCount(of: 1)

defer {
dataStore.index = dataStore.index.advanced(by: length)
}
guard let string = String(data: dataStore.data[dataStore.index..<dataStore.index.advanced(by: length - 1)], encoding: .utf8) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
throw DecodingError.dataCorrupted(context)
}
return string
}

@inline(__always)
func readData() throws -> Data {
let length = try readCheckBlockCount(of: 1)
defer {
dataStore.index = dataStore.index.advanced(by: length)
}
return dataStore.data.subdata(in: dataStore.index..<dataStore.index.advanced(by: length))
}
}
58 changes: 24 additions & 34 deletions Sources/CDRCodable/Decoder/KeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,45 @@ extension _CDRDecoder {

extension _CDRDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
func contains(_ key: Key) -> Bool {
return true
true
}

func decodeNil(forKey key: Key) throws -> Bool {
return true
true
}

func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
return try readByte() != 0
try read(UInt8.self) != 0
}

func decode(_ type: String.Type, forKey key: Key) throws -> String {
let length = Int(try read(UInt32.self))
let data = try read(length - 1)
_ = try readByte()

guard let string = String(data: data, encoding: .utf8) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
throw DecodingError.dataCorrupted(context)
}
return string
try readString()
}

func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
let bitPattern = try read(UInt64.self)
return Double(bitPattern: bitPattern)
}

func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
let bitPattern = try read(UInt32.self)
return Float(bitPattern: bitPattern)
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : FixedWidthInteger & Decodable {
guard let t = T(exactly: try read(T.self)) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid binary integer format")
throw DecodingError.typeMismatch(T.self, context)
}
return t
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Numeric & Decodable {
try read(T.self)
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
if T.self is Data.Type {
let length = Int(try read(UInt32.self))
return try read(length) as! T
switch T.self {
case is [Double].Type: return try readArray(Double.self) as! T
case is [Float].Type: return try readArray(Float.self) as! T
case is [Int].Type: return try readArray(Int.self) as! T
case is [Int8].Type: return try readArray(Int8.self) as! T
case is [Int16].Type: return try readArray(Int16.self) as! T
case is [Int32].Type: return try readArray(Int32.self) as! T
case is [Int64].Type: return try readArray(Int64.self) as! T
case is [UInt].Type: return try readArray(UInt.self) as! T
case is [UInt8].Type: return try readArray(UInt8.self) as! T
case is [UInt16].Type: return try readArray(UInt16.self) as! T
case is [UInt32].Type: return try readArray(UInt32.self) as! T
case is [UInt64].Type: return try readArray(UInt64.self) as! T
case is Data.Type:
return try readData() as! T
default:
let decoder = _CDRDecoder(data: self.dataStore)
return try T(from: decoder)
}
let decoder = _CDRDecoder(data: self.dataStore)
let value = try T(from: decoder)
return value
}

func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
Expand Down
38 changes: 7 additions & 31 deletions Sources/CDRCodable/Decoder/SingleValueDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,28 @@ extension _CDRDecoder {

extension _CDRDecoder.SingleValueContainer: SingleValueDecodingContainer {
func decodeNil() -> Bool {
return true
true
}

func decode(_ type: Bool.Type) throws -> Bool {
return try readByte() != 0
try read(UInt8.self) != 0
}

func decode(_ type: String.Type) throws -> String {
let length = Int(try read(UInt32.self))
let data = try read(length - 1)
_ = try readByte()

guard let string = String(data: data, encoding: .utf8) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
throw DecodingError.dataCorrupted(context)
}
return string
try readString()
}

func decode(_ type: Double.Type) throws -> Double {
let bitPattern = try read(UInt64.self)
return Double(bitPattern: bitPattern)
}

func decode(_ type: Float.Type) throws -> Float {
let bitPattern = try read(UInt32.self)
return Float(bitPattern: bitPattern)
}

func decode<T>(_ type: T.Type) throws -> T where T : FixedWidthInteger & Decodable {
guard let t = T(exactly: try read(T.self)) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Invalid binary integer format")
throw DecodingError.typeMismatch(T.self, context)
}
return t
func decode<T>(_ type: T.Type) throws -> T where T : Numeric & Decodable {
try read(T.self)
}

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
switch type {
case is Data.Type:
let length = Int(try read(UInt32.self))
return try read(length) as! T
return try readData() as! T
default:
let decoder = _CDRDecoder(data: self.dataStore)
let value = try T(from: decoder)
return value
return try T(from: decoder)
}
}
}
Expand Down
4 changes: 0 additions & 4 deletions Sources/CDRCodable/FixedWidthInteger+Bytes.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
extension FixedWidthInteger {
init(bytes: [UInt8]) {
self = bytes.withUnsafeBytes { $0.load(as: Self.self) }.littleEndian
}

var bytes: [UInt8] {
withUnsafeBytes(of: self, Array.init)
}
Expand Down
27 changes: 27 additions & 0 deletions Tests/CDRCodableTests/CDRCodablePerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ class CDRCodablePerformanceTests: XCTestCase {
}
}

func testPerformanceUnkeyedArrayDecode() {
let testArray = [Int16].init(repeating: 55, count: 40 * 1024)
let cdrData = try! encoder.encode(testArray)

self.measure {
for _ in 1...100 {
let testArray1 = try! decoder.decode([Int16].self, from: cdrData)
XCTAssertEqual(testArray1[0], 55)
}
}
}

func testPerformanceKeyedArrayEncode() {
struct TestStruct: Codable {
let a: [Int16]
Expand All @@ -73,4 +85,19 @@ class CDRCodablePerformanceTests: XCTestCase {
}
}

func testPerformanceKeyedArrayDecode() {
struct TestStruct: Codable {
let a: [Int16]
}
let testStruct = TestStruct(a: .init(repeating: 55, count: 40 * 1024))
let cdrData = try! encoder.encode(testStruct)

self.measure {
for _ in 1...100 {
let testStruct1 = try! decoder.decode(TestStruct.self, from: cdrData)
XCTAssertEqual(testStruct1.a[0], 55)
}
}
}

}

0 comments on commit 11bb1ee

Please sign in to comment.