From 5f2bf9c712609df83bd7ec67607381981c3abec0 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Thu, 6 Aug 2026 10:50:49 -0700 Subject: [PATCH 1/2] Reduce CPU usage for Deserializer --- .../SwiftNetwork/Utilities/Deserializer.swift | 170 ++++++++++++++++-- .../Utilities/SerializationHelpers.swift | 2 + 2 files changed, 162 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftNetwork/Utilities/Deserializer.swift b/Sources/SwiftNetwork/Utilities/Deserializer.swift index 7f48d2d..7dd1012 100644 --- a/Sources/SwiftNetwork/Utilities/Deserializer.swift +++ b/Sources/SwiftNetwork/Utilities/Deserializer.swift @@ -42,6 +42,7 @@ public enum DeserializationResult: CustomStringConvertible, Equatable, Sendable case success(parsedBytes: Int, remainingBytes: Int) case error(DeserializationError) + @usableFromInline static let success: Self = .success(parsedBytes: 0, remainingBytes: 0) public var description: String { @@ -76,11 +77,11 @@ public enum DeserializationResult: CustomStringConvertible, Equatable, Sendable @available(Network 0.1.0, *) public struct Deserializer: ~Copyable, ~Escapable { private var factory: Factory - private var currentSpan: RawSpan - private var currentSpanByteCount = 0 + @usableFromInline internal var currentSpan: RawSpan + @usableFromInline internal var currentSpanByteCount = 0 private var availableByteCount: Int private var scratchSpace: [16 of UInt8]? // Initialized lazily - private var cursor = 0 + @usableFromInline internal var cursor = 0 private var previousSpanAggregateByteCount = 0 private(set) var internalResult: DeserializationResult = .success @@ -339,11 +340,32 @@ public struct Deserializer= 2 else { + // Fallback for multi-span cases + try readUInt16AtSpanBoundary(&value) + return + } + value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt16.self) + // Move by two bytes + cursor &+= 2 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint16(_ value: inout UInt16?) throws(DeserializationError) { + var temp: UInt16 = 0 + try uint16(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt16AtSpanBoundary(_ value: inout UInt16) throws(DeserializationError) { try readFixedSize(&value) } @@ -387,11 +430,32 @@ public struct Deserializer= 4 else { + // Fallback for multi-span cases + try readUInt32AtSpanBoundary(&value) + return + } + value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt32.self) + // Move by four bytes + cursor &+= 4 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint32(_ value: inout UInt32?) throws(DeserializationError) { + var temp: UInt32 = 0 + try uint32(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt32AtSpanBoundary(_ value: inout UInt32) throws(DeserializationError) { try readFixedSize(&value) } @@ -399,11 +463,32 @@ public struct Deserializer= 8 else { + // Fallback for multi-span cases + try readUInt64AtSpanBoundary(&value) + return + } + value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt64.self) + // Move by eight bytes + cursor &+= 8 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint64(_ value: inout UInt64?) throws(DeserializationError) { + var temp: UInt64 = 0 + try uint64(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt64AtSpanBoundary(_ value: inout UInt64) throws(DeserializationError) { try readFixedSize(&value) } @@ -411,11 +496,32 @@ public struct Deserializer= 2 else { + // Fallback for multi-span cases + try readUInt16NBOAtSpanBoundary(&value) + return + } + value = UInt16(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt16.self)) + // Move by two bytes + cursor &+= 2 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint16NetworkByteOrder(_ value: inout UInt16?) throws(DeserializationError) { + var temp: UInt16 = 0 + try uint16NetworkByteOrder(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt16NBOAtSpanBoundary(_ value: inout UInt16) throws(DeserializationError) { try readFixedSize(&value, networkByteOrder: true) } @@ -423,11 +529,32 @@ public struct Deserializer= 4 else { + // Fallback for multi-span cases + try readUInt32NBOAtSpanBoundary(&value) + return + } + value = UInt32(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt32.self)) + // Move by four bytes + cursor &+= 4 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint32NetworkByteOrder(_ value: inout UInt32?) throws(DeserializationError) { + var temp: UInt32 = 0 + try uint32NetworkByteOrder(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt32NBOAtSpanBoundary(_ value: inout UInt32) throws(DeserializationError) { try readFixedSize(&value, networkByteOrder: true) } @@ -435,11 +562,32 @@ public struct Deserializer= 8 else { + // Fallback for multi-span cases + try readUInt64NBOAtSpanBoundary(&value) + return + } + value = UInt64(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt64.self)) + // Move by eight bytes + cursor &+= 8 } + @_optimize(speed) + @inlinable + @inline(__always) public mutating func uint64NetworkByteOrder(_ value: inout UInt64?) throws(DeserializationError) { + var temp: UInt64 = 0 + try uint64NetworkByteOrder(&temp) + value = temp + } + + @usableFromInline + @inline(never) + internal mutating func readUInt64NBOAtSpanBoundary(_ value: inout UInt64) throws(DeserializationError) { try readFixedSize(&value, networkByteOrder: true) } @@ -827,6 +975,7 @@ public struct Deserializer FrameArray { frameArray } @@ -119,6 +120,7 @@ public struct FrameArraySpanFactory: ~Copyable, ~Escapable, DeserializerSpanFact // lifetime dependency. @_lifetime(immortal) is correct for a self-contained // ~Escapable type whose data is fully owned. @_lifetime(immortal) + @usableFromInline init(_ frameArray: consuming FrameArray) { self.spanCount = frameArray.count self.availableByteCount = frameArray.unclaimedLength From 5f194b7af3ed9ab2f5c5691377e9d63f10e18110 Mon Sep 17 00:00:00 2001 From: agnosticdev Date: Thu, 6 Aug 2026 11:07:14 -0700 Subject: [PATCH 2/2] Use the memory layout of the type --- .../SwiftNetwork/Utilities/Deserializer.swift | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftNetwork/Utilities/Deserializer.swift b/Sources/SwiftNetwork/Utilities/Deserializer.swift index 7dd1012..fdb6a0d 100644 --- a/Sources/SwiftNetwork/Utilities/Deserializer.swift +++ b/Sources/SwiftNetwork/Utilities/Deserializer.swift @@ -351,7 +351,7 @@ public struct Deserializer.size } @_optimize(speed) @@ -389,14 +389,13 @@ public struct Deserializer= 2 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt16AtSpanBoundary(&value) return } value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt16.self) - // Move by two bytes - cursor &+= 2 + cursor &+= MemoryLayout.size } @_optimize(speed) @@ -434,14 +433,13 @@ public struct Deserializer= 4 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt32AtSpanBoundary(&value) return } value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt32.self) - // Move by four bytes - cursor &+= 4 + cursor &+= MemoryLayout.size } @_optimize(speed) @@ -467,14 +465,14 @@ public struct Deserializer= 8 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt64AtSpanBoundary(&value) return } value = currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt64.self) // Move by eight bytes - cursor &+= 8 + cursor &+= MemoryLayout.size } @_optimize(speed) @@ -500,14 +498,13 @@ public struct Deserializer= 2 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt16NBOAtSpanBoundary(&value) return } value = UInt16(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt16.self)) - // Move by two bytes - cursor &+= 2 + cursor &+= MemoryLayout.size } @_optimize(speed) @@ -533,14 +530,13 @@ public struct Deserializer= 4 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt32NBOAtSpanBoundary(&value) return } value = UInt32(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt32.self)) - // Move by four bytes - cursor &+= 4 + cursor &+= MemoryLayout.size } @_optimize(speed) @@ -566,14 +562,13 @@ public struct Deserializer= 8 else { + guard (currentSpanByteCount - cursor) >= MemoryLayout.size else { // Fallback for multi-span cases try readUInt64NBOAtSpanBoundary(&value) return } value = UInt64(bigEndian: currentSpan.unsafeLoadUnaligned(fromByteOffset: cursor, as: UInt64.self)) - // Move by eight bytes - cursor &+= 8 + cursor &+= MemoryLayout.size } @_optimize(speed)