Skip to content

Commit 89deb2a

Browse files
committed
Add a fast-path for equality checking when the backing is empty or pointer-equal
1 parent e072f82 commit 89deb2a

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-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: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2710,13 +2710,34 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
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 {
27122712
let length1 = d1.count
2713-
if length1 != d2.count {
2713+
let length2 = d2.count
2714+
2715+
// Unequal length data can never be equal
2716+
guard length1 == length2 else {
27142717
return false
27152718
}
2719+
2720+
// See if both are empty
2721+
switch (d1._representation, d2._representation) {
2722+
case (.empty, .empty):
2723+
return true
2724+
default:
2725+
break
2726+
}
2727+
27162728
if length1 > 0 {
27172729
return d1.withUnsafeBytes { (b1: UnsafeRawBufferPointer) in
27182730
return d2.withUnsafeBytes { (b2: UnsafeRawBufferPointer) in
2719-
return memcmp(b1.baseAddress!, b2.baseAddress!, b2.count) == 0
2731+
// If they have the same base address and same count, it is equal
2732+
let b1Address = b1.baseAddress!
2733+
let b2Address = b2.baseAddress!
2734+
2735+
if b1Address == b2Address {
2736+
return true
2737+
} else {
2738+
// Compare the contents
2739+
return memcmp(b1.baseAddress!, b2.baseAddress!, b2.count) == 0
2740+
}
27202741
}
27212742
}
27222743
}

0 commit comments

Comments
 (0)