Skip to content

Commit

Permalink
Optimize encoding keyed arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaRU committed Jan 29, 2024
1 parent ab6193c commit 9112d6f
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 22 deletions.
44 changes: 32 additions & 12 deletions Sources/CDRCodable/Encoder/KeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ extension _CDREncoder {
}

extension _CDREncoder.KeyedContainer: KeyedEncodingContainerProtocol {
func encodeNil(forKey key: Key) throws {
@inline(__always)
private func encodeNumericArray(count: Int, size: Int, pointer: UnsafeRawBufferPointer) throws {
guard let uint32 = UInt32(exactly: count) else {
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode data of length \(count).")
throw EncodingError.invalidValue(count, context)
}
write(value: uint32)
self.data.data.append(pointer.baseAddress!.assumingMemoryBound(to: UInt8.self), count: count * size)
}

func encodeNil(forKey key: Key) throws {
}

func encode(_ value: Bool, forKey key: Key) throws {
switch value {
case false:
Expand Down Expand Up @@ -61,20 +71,30 @@ extension _CDREncoder.KeyedContainer: KeyedEncodingContainerProtocol {
}

func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
if let value = value as? Data {
let length = value.count
if let uint32 = UInt32(exactly: length) {
write(value: uint32)
write(data: value)
} else {
switch value {
case let value as [Int]: try encodeNumericArray(count: value.count, size: MemoryLayout<Int>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int8]: try encodeNumericArray(count: value.count, size: MemoryLayout<Int8>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int16]: try encodeNumericArray(count: value.count, size: MemoryLayout<Int16>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int32]: try encodeNumericArray(count: value.count, size: MemoryLayout<Int32>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int64]: try encodeNumericArray(count: value.count, size: MemoryLayout<Int64>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt]: try encodeNumericArray(count: value.count, size: MemoryLayout<UInt>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt8]: try encodeNumericArray(count: value.count, size: MemoryLayout<UInt8>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt16]: try encodeNumericArray(count: value.count, size: MemoryLayout<UInt16>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt32]: try encodeNumericArray(count: value.count, size: MemoryLayout<UInt32>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt64]: try encodeNumericArray(count: value.count, size: MemoryLayout<UInt64>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Float]: try encodeNumericArray(count: value.count, size: MemoryLayout<Float>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Double]: try encodeNumericArray(count: value.count, size: MemoryLayout<Double>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as Data:
guard let uint32 = UInt32(exactly: value.count) else {
let context = EncodingError.Context(codingPath: self.codingPath, debugDescription: "Cannot encode data of length \(value.count).")
throw EncodingError.invalidValue(value, context)
throw EncodingError.invalidValue(value.count, context)
}
return
write(value: uint32)
write(data: value)
default:
let encoder = _CDREncoder(data: self.data)
try value.encode(to: encoder)
}

let encoder = _CDREncoder(data: self.data)
try value.encode(to: encoder)
}

private func nestedSingleValueContainer(forKey key: Key) -> SingleValueEncodingContainer {
Expand Down
12 changes: 12 additions & 0 deletions Tests/CDRCodableTests/CDRCodableEncodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,16 @@ class CDRCodableEncodingTests: XCTestCase {
let value = try! encoder.encode(data)
XCTAssertEqual(value, Data([5, 0, 0, 0, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0, 0, 0]))
}

func testEncodeStruct() {
struct TestStruct: Codable {
let i: Int16
let s: String
let a: [Int16]
}

let value = TestStruct(i: 0x55, s: "Test string", a: [1,2,3,4,5,6,7,8,9])
let data = try! encoder.encode(value)
XCTAssertEqual(data, Data([0x55, 0, 0, 0, 0x0c, 0, 0, 0, 0x54, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0, 0x09, 0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 0, 0]))
}
}
40 changes: 30 additions & 10 deletions Tests/CDRCodableTests/CDRCodablePerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ class CDRCodablePerformanceTests: XCTestCase {
let key: String
let stamp: UInt64
}

let encoder = CDREncoder()
let decoder = CDRDecoder()

func testDataDecodeEncode() {
let testData = Data(repeating: 1, count: 40 * 1024)
let imageData = ImageData(image: testData, key: "Key", stamp: 123456789)

let encoder = CDREncoder()
let cdrData = try! encoder.encode(imageData)
XCTAssertEqual(cdrData.count, 40984)

let decoder = CDRDecoder()
let decodedImage = try! decoder.decode(ImageData.self, from: cdrData)
XCTAssertEqual(decodedImage.key, "Key")
}
Expand All @@ -26,13 +26,10 @@ class CDRCodablePerformanceTests: XCTestCase {
let testData = Data(repeating: 1, count: 40 * 1024)
let imageData = ImageData(image: testData, key: "Key", stamp: 123456789)

let encoder = CDREncoder()
let cdrData = try! encoder.encode(imageData)

let decoder = CDRDecoder()

self.measure {
for _ in 1...100 {
for _ in 1...1000 {
let decodedImage = try! decoder.decode(ImageData.self, from: cdrData)
XCTAssertEqual(decodedImage.key, "Key")
}
Expand All @@ -43,14 +40,37 @@ class CDRCodablePerformanceTests: XCTestCase {
let testData = Data(repeating: 1, count: 40 * 1024)
let imageData = ImageData(image: testData, key: "Key", stamp: 123456789)

let encoder = CDREncoder()

self.measure {
for _ in 1...100 {
for _ in 1...1000 {
let cdrData = try! encoder.encode(imageData)
XCTAssertEqual(cdrData.count, 40984)
}
}
}

func testPerformanceUnkeyedArrayEncode() {
let testArray = [Int16].init(repeating: 55, count: 40 * 1024)

self.measure {
for _ in 1...100 {
let cdrData = try! encoder.encode(testArray)
XCTAssertEqual(cdrData.count, 81924)
}
}
}

func testPerformanceKeyedArrayEncode() {
struct TestStruct: Codable {
let a: [Int16]
}
let testStruct = TestStruct(a: .init(repeating: 55, count: 40 * 1024))

self.measure {
for _ in 1...100 {
let cdrData = try! encoder.encode(testStruct)
XCTAssertEqual(cdrData.count, 81924)
}
}
}

}

0 comments on commit 9112d6f

Please sign in to comment.