Skip to content

Commit 5db7965

Browse files
authored
Add a fast-path for equality checking when the backing is empty or pointer equal (#1322)
* Add a fast-path for equality checking when the backing is empty or pointer-equal * Address review feedback
1 parent 147607e commit 5db7965

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,91 @@ let benchmarks = {
3434
assert(u1 != u2)
3535
}
3636
}
37+
38+
// MARK: Data
39+
40+
func createSomeData(_ length: Int) -> Data {
41+
var d = Data(repeating: 42, count: length)
42+
// Set a byte to be another value just so we know we have a unique pointer to the backing
43+
// For maximum inefficiency in the not equal case, set the last byte
44+
d[length - 1] = UInt8.random(in: UInt8.min..<UInt8.max)
45+
return d
46+
}
47+
48+
/// A box `Data`. Intentionally turns the value type into a reference, so we can make a promise that the inner value is not copied due to mutation during a test of insertion or replacing.
49+
class TwoDatasBox {
50+
var d1: Data
51+
var d2: Data
52+
53+
init(d1: Data, d2: Data) {
54+
self.d1 = d1
55+
self.d2 = d2
56+
}
57+
}
58+
59+
// MARK: -
60+
61+
Benchmark("DataEqualEmpty", closure: { benchmark, box in
62+
blackHole(box.d1 == box.d2)
63+
}, setup: { () -> TwoDatasBox in
64+
let d1 = Data()
65+
let d2 = d1
66+
let box = TwoDatasBox(d1: d1, d2: d2)
67+
return box
68+
})
69+
70+
Benchmark("DataEqualInline", closure: { benchmark, box in
71+
blackHole(box.d1 == box.d2)
72+
}, setup: { () -> TwoDatasBox in
73+
let d1 = createSomeData(12) // Less than size of InlineData.Buffer
74+
let d2 = d1
75+
let box = TwoDatasBox(d1: d1, d2: d2)
76+
return box
77+
})
78+
79+
Benchmark("DataNotEqualInline", closure: { benchmark, box in
80+
blackHole(box.d1 != box.d2)
81+
}, setup: { () -> TwoDatasBox in
82+
let d1 = createSomeData(12) // Less than size of InlineData.Buffer
83+
let d2 = createSomeData(12)
84+
let box = TwoDatasBox(d1: d1, d2: d2)
85+
return box
86+
})
87+
88+
Benchmark("DataEqualLarge", closure: { benchmark, box in
89+
blackHole(box.d1 == box.d2)
90+
}, setup: { () -> TwoDatasBox in
91+
let d1 = createSomeData(1024 * 8)
92+
let d2 = d1
93+
let box = TwoDatasBox(d1: d1, d2: d2)
94+
return box
95+
})
96+
97+
Benchmark("DataNotEqualLarge", closure: { benchmark, box in
98+
blackHole(box.d1 != box.d2)
99+
}, setup: { () -> TwoDatasBox in
100+
let d1 = createSomeData(1024 * 8)
101+
let d2 = createSomeData(1024 * 8)
102+
let box = TwoDatasBox(d1: d1, d2: d2)
103+
return box
104+
})
105+
106+
Benchmark("DataEqualReallyLarge", closure: { benchmark, box in
107+
blackHole(box.d1 == box.d2)
108+
}, setup: { () -> TwoDatasBox in
109+
let d1 = createSomeData(1024 * 1024 * 8)
110+
let d2 = d1
111+
let box = TwoDatasBox(d1: d1, d2: d2)
112+
return box
113+
})
114+
115+
Benchmark("DataNotEqualReallyLarge", closure: { benchmark, box in
116+
blackHole(box.d1 != box.d2)
117+
}, setup: { () -> TwoDatasBox in
118+
let d1 = createSomeData(1024 * 1024 * 8)
119+
let d2 = createSomeData(1024 * 1024 * 8)
120+
let box = TwoDatasBox(d1: d1, d2: d2)
121+
return box
122+
})
123+
37124
}

Sources/FoundationEssentials/Data/Data.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2709,14 +2709,36 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
27092709
/// Returns `true` if the two `Data` arguments are equal.
27102710
@inlinable // This is @inlinable as emission into clients is safe -- the concept of equality on Data will not change.
27112711
public static func ==(d1 : Data, d2 : Data) -> Bool {
2712+
// See if both are empty
2713+
switch (d1._representation, d2._representation) {
2714+
case (.empty, .empty):
2715+
return true
2716+
default:
2717+
// Continue on to checks below
2718+
break
2719+
}
2720+
27122721
let length1 = d1.count
2713-
if length1 != d2.count {
2722+
let length2 = d2.count
2723+
2724+
// Unequal length data can never be equal
2725+
guard length1 == length2 else {
27142726
return false
27152727
}
2728+
27162729
if length1 > 0 {
27172730
return d1.withUnsafeBytes { (b1: UnsafeRawBufferPointer) in
27182731
return d2.withUnsafeBytes { (b2: UnsafeRawBufferPointer) in
2719-
return memcmp(b1.baseAddress!, b2.baseAddress!, b2.count) == 0
2732+
// If they have the same base address and same count, it is equal
2733+
let b1Address = b1.baseAddress!
2734+
let b2Address = b2.baseAddress!
2735+
2736+
guard b1Address != b2Address else {
2737+
return true
2738+
}
2739+
2740+
// Compare the contents
2741+
return memcmp(b1Address, b2Address, b2.count) == 0
27202742
}
27212743
}
27222744
}

0 commit comments

Comments
 (0)