Skip to content

Commit 3da4b79

Browse files
authored
Improve performance working with ID bytes (#18)
1 parent c40cd5c commit 3da4b79

File tree

8 files changed

+343
-199
lines changed

8 files changed

+343
-199
lines changed

Sources/W3CTraceContext/SpanID.swift

Lines changed: 111 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,23 @@
1616
///
1717
/// [W3C TraceContext: parent-id](https://www.w3.org/TR/trace-context-1/#parent-id)
1818
public struct SpanID: Sendable {
19-
private let _bytes: Bytes
20-
21-
/// An 8-byte array representation of the span ID.
22-
public var bytes: [UInt8] {
23-
withUnsafeBytes(of: _bytes, Array.init)
24-
}
19+
/// The 8 bytes making up the span ID.
20+
public let bytes: Bytes
2521

2622
/// Create a span ID from 8 bytes.
2723
///
2824
/// - Parameter bytes: The 8 bytes making up the span ID.
2925
public init(bytes: Bytes) {
30-
_bytes = bytes
26+
self.bytes = bytes
3127
}
3228

3329
/// Create a random span ID using the given random number generator.
3430
///
3531
/// - Parameter randomNumberGenerator: The random number generator used to create random bytes for the span ID.
3632
/// - Returns: A random span ID.
3733
public static func random(using randomNumberGenerator: inout some RandomNumberGenerator) -> SpanID {
38-
var bytes: SpanID.Bytes = (0, 0, 0, 0, 0, 0, 0, 0)
39-
withUnsafeMutableBytes(of: &bytes) { ptr in
34+
var bytes: SpanID.Bytes = .null
35+
bytes.withUnsafeMutableBytes { ptr in
4036
ptr.storeBytes(of: randomNumberGenerator.next().bigEndian, as: UInt64.self)
4137
}
4238
return SpanID(bytes: bytes)
@@ -49,68 +45,126 @@ public struct SpanID: Sendable {
4945
var generator = SystemRandomNumberGenerator()
5046
return random(using: &generator)
5147
}
52-
53-
/// An 8-byte array.
54-
public typealias Bytes = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
5548
}
5649

57-
extension SpanID: Equatable {
58-
public static func == (lhs: Self, rhs: Self) -> Bool {
59-
lhs._bytes.0 == rhs._bytes.0
60-
&& lhs._bytes.1 == rhs._bytes.1
61-
&& lhs._bytes.2 == rhs._bytes.2
62-
&& lhs._bytes.3 == rhs._bytes.3
63-
&& lhs._bytes.4 == rhs._bytes.4
64-
&& lhs._bytes.5 == rhs._bytes.5
65-
&& lhs._bytes.6 == rhs._bytes.6
66-
&& lhs._bytes.7 == rhs._bytes.7
67-
}
68-
}
50+
extension SpanID: Equatable {}
6951

70-
extension SpanID: Hashable {
71-
public func hash(into hasher: inout Hasher) {
72-
hasher.combine(_bytes.0)
73-
hasher.combine(_bytes.1)
74-
hasher.combine(_bytes.2)
75-
hasher.combine(_bytes.3)
76-
hasher.combine(_bytes.4)
77-
hasher.combine(_bytes.5)
78-
hasher.combine(_bytes.6)
79-
hasher.combine(_bytes.7)
80-
}
81-
}
52+
extension SpanID: Hashable {}
8253

8354
extension SpanID: Identifiable {
84-
public var id: [UInt8] { bytes }
55+
public var id: Self { self }
8556
}
8657

8758
extension SpanID: CustomStringConvertible {
8859
/// A 16 character hex string representation of the span ID.
60+
public var description: String {
61+
"\(bytes)"
62+
}
63+
}
64+
65+
// MARK: - Bytes
66+
67+
extension SpanID {
68+
/// An 8-byte array.
69+
public struct Bytes: Collection, Equatable, Hashable, Sendable {
70+
public static var null: Self { SpanID.Bytes((0, 0, 0, 0, 0, 0, 0, 0)) }
71+
72+
@usableFromInline
73+
var _bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
74+
75+
public init(_ bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) {
76+
_bytes = bytes
77+
}
78+
79+
public subscript(position: Int) -> UInt8 {
80+
switch position {
81+
case 0: _bytes.0
82+
case 1: _bytes.1
83+
case 2: _bytes.2
84+
case 3: _bytes.3
85+
case 4: _bytes.4
86+
case 5: _bytes.5
87+
case 6: _bytes.6
88+
case 7: _bytes.7
89+
default: fatalError("Index out of range")
90+
}
91+
}
92+
93+
public func index(after i: Int) -> Int {
94+
precondition(i < endIndex, "Can't advance beyond endIndex")
95+
return i + 1
96+
}
97+
98+
public var startIndex: Int { 0 }
99+
public var endIndex: Int { 8 }
100+
101+
@inlinable
102+
public func withContiguousStorageIfAvailable<Result>(
103+
_ body: (UnsafeBufferPointer<UInt8>) throws -> Result
104+
) rethrows -> Result? {
105+
try Swift.withUnsafeBytes(of: _bytes) { bytes in
106+
try bytes.withMemoryRebound(to: UInt8.self, body)
107+
}
108+
}
109+
110+
/// Calls the given closure with a pointer to the span ID's underlying bytes.
111+
///
112+
/// - Parameter body: A closure receiving an `UnsafeRawBufferPointer` to the span ID's underlying bytes.
113+
@inlinable
114+
public func withUnsafeBytes<Result>(_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result {
115+
try Swift.withUnsafeBytes(of: _bytes, body)
116+
}
117+
118+
/// Calls the given closure with a mutable pointer to the span ID's underlying bytes.
119+
///
120+
/// - Parameter body: A closure receiving an `UnsafeMutableRawBufferPointer` to the span ID's underlying bytes.
121+
@inlinable
122+
public mutating func withUnsafeMutableBytes<Result>(
123+
_ body: (UnsafeMutableRawBufferPointer) throws -> Result
124+
) rethrows -> Result {
125+
try Swift.withUnsafeMutableBytes(of: &_bytes) { bytes in
126+
try body(bytes)
127+
}
128+
}
129+
130+
public static func == (lhs: Self, rhs: Self) -> Bool {
131+
lhs._bytes.0 == rhs._bytes.0
132+
&& lhs._bytes.1 == rhs._bytes.1
133+
&& lhs._bytes.2 == rhs._bytes.2
134+
&& lhs._bytes.3 == rhs._bytes.3
135+
&& lhs._bytes.4 == rhs._bytes.4
136+
&& lhs._bytes.5 == rhs._bytes.5
137+
&& lhs._bytes.6 == rhs._bytes.6
138+
&& lhs._bytes.7 == rhs._bytes.7
139+
}
140+
141+
public func hash(into hasher: inout Hasher) {
142+
hasher.combine(_bytes.0)
143+
hasher.combine(_bytes.1)
144+
hasher.combine(_bytes.2)
145+
hasher.combine(_bytes.3)
146+
hasher.combine(_bytes.4)
147+
hasher.combine(_bytes.5)
148+
hasher.combine(_bytes.6)
149+
hasher.combine(_bytes.7)
150+
}
151+
}
152+
}
153+
154+
extension SpanID.Bytes: CustomStringConvertible {
155+
/// A 16 character hex string representation of the bytes.
89156
public var description: String {
90157
String(decoding: hexBytes, as: UTF8.self)
91158
}
92159

93-
/// A 16 character UTF-8 hex byte array representation of the span ID.
160+
/// A 16 character UTF-8 hex byte array representation of the bytes.
94161
public var hexBytes: [UInt8] {
95-
var asciiBytes: (UInt64, UInt64) = (0, 0)
96-
return withUnsafeMutableBytes(of: &asciiBytes) { ptr in
97-
ptr[0] = Hex.lookup[Int(_bytes.0 >> 4)]
98-
ptr[1] = Hex.lookup[Int(_bytes.0 & 0x0F)]
99-
ptr[2] = Hex.lookup[Int(_bytes.1 >> 4)]
100-
ptr[3] = Hex.lookup[Int(_bytes.1 & 0x0F)]
101-
ptr[4] = Hex.lookup[Int(_bytes.2 >> 4)]
102-
ptr[5] = Hex.lookup[Int(_bytes.2 & 0x0F)]
103-
ptr[6] = Hex.lookup[Int(_bytes.3 >> 4)]
104-
ptr[7] = Hex.lookup[Int(_bytes.3 & 0x0F)]
105-
ptr[8] = Hex.lookup[Int(_bytes.4 >> 4)]
106-
ptr[9] = Hex.lookup[Int(_bytes.4 & 0x0F)]
107-
ptr[10] = Hex.lookup[Int(_bytes.5 >> 4)]
108-
ptr[11] = Hex.lookup[Int(_bytes.5 & 0x0F)]
109-
ptr[12] = Hex.lookup[Int(_bytes.6 >> 4)]
110-
ptr[13] = Hex.lookup[Int(_bytes.6 & 0x0F)]
111-
ptr[14] = Hex.lookup[Int(_bytes.7 >> 4)]
112-
ptr[15] = Hex.lookup[Int(_bytes.7 & 0x0F)]
113-
return Array(ptr)
162+
var asciiBytes = [UInt8](repeating: 0, count: 16)
163+
for i in startIndex ..< endIndex {
164+
let byte = self[i]
165+
asciiBytes[2 * i] = Hex.lookup[Int(byte >> 4)]
166+
asciiBytes[2 * i + 1] = Hex.lookup[Int(byte & 0x0F)]
114167
}
168+
return asciiBytes
115169
}
116170
}

Sources/W3CTraceContext/TraceContext.swift

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -69,47 +69,23 @@ public struct TraceContext: Sendable {
6969

7070
// trace ID
7171

72-
var traceIDBytes = TraceID.Bytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
72+
var traceIDBytes = TraceID.Bytes.null
7373
withUnsafeMutableBytes(of: &traceIDBytes) { ptr in
7474
Hex.convert(traceParent[3 ..< 35], toBytes: ptr)
7575
}
76-
if traceIDBytes.0 == 0,
77-
traceIDBytes.1 == 0,
78-
traceIDBytes.2 == 0,
79-
traceIDBytes.3 == 0,
80-
traceIDBytes.4 == 0,
81-
traceIDBytes.5 == 0,
82-
traceIDBytes.6 == 0,
83-
traceIDBytes.7 == 0,
84-
traceIDBytes.8 == 0,
85-
traceIDBytes.9 == 0,
86-
traceIDBytes.10 == 0,
87-
traceIDBytes.11 == 0,
88-
traceIDBytes.12 == 0,
89-
traceIDBytes.13 == 0,
90-
traceIDBytes.14 == 0,
91-
traceIDBytes.15 == 0
92-
{
76+
if traceIDBytes == .null {
9377
throw TraceParentDecodingError(
9478
.invalidTraceID(String(decoding: traceParent[3 ..< 35], as: UTF8.self))
9579
)
9680
}
9781

9882
// span ID
9983

100-
var spanIDBytes = SpanID.Bytes(0, 0, 0, 0, 0, 0, 0, 0)
101-
withUnsafeMutableBytes(of: &spanIDBytes) { ptr in
84+
var spanIDBytes = SpanID.Bytes.null
85+
spanIDBytes.withUnsafeMutableBytes { ptr in
10286
Hex.convert(traceParent[36 ..< 52], toBytes: ptr)
10387
}
104-
if spanIDBytes.0 == 0,
105-
spanIDBytes.1 == 0,
106-
spanIDBytes.2 == 0,
107-
spanIDBytes.3 == 0,
108-
spanIDBytes.4 == 0,
109-
spanIDBytes.5 == 0,
110-
spanIDBytes.6 == 0,
111-
spanIDBytes.7 == 0
112-
{
88+
if spanIDBytes == .null {
11389
throw TraceParentDecodingError(
11490
.invalidSpanID(String(decoding: traceParent[36 ..< 52], as: UTF8.self))
11591
)

0 commit comments

Comments
 (0)