Skip to content

Commit 5c12225

Browse files
committed
data identical
1 parent 23013ca commit 5c12225

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ let benchmarks = {
6767
return box
6868
})
6969

70+
Benchmark("DataIdenticalEmpty", closure: { benchmark, box in
71+
blackHole(box.d1.isIdentical(to: box.d2))
72+
}, setup: { () -> TwoDatasBox in
73+
let d1 = Data()
74+
let d2 = d1
75+
let box = TwoDatasBox(d1: d1, d2: d2)
76+
return box
77+
})
78+
7079
Benchmark("DataEqualInline", closure: { benchmark, box in
7180
blackHole(box.d1 == box.d2)
7281
}, setup: { () -> TwoDatasBox in
@@ -75,6 +84,15 @@ let benchmarks = {
7584
let box = TwoDatasBox(d1: d1, d2: d2)
7685
return box
7786
})
87+
88+
Benchmark("DataIdenticalInline", closure: { benchmark, box in
89+
blackHole(box.d1.isIdentical(to: box.d2))
90+
}, setup: { () -> TwoDatasBox in
91+
let d1 = createSomeData(12) // Less than size of InlineData.Buffer
92+
let d2 = d1
93+
let box = TwoDatasBox(d1: d1, d2: d2)
94+
return box
95+
})
7896

7997
Benchmark("DataNotEqualInline", closure: { benchmark, box in
8098
blackHole(box.d1 != box.d2)
@@ -93,7 +111,16 @@ let benchmarks = {
93111
let box = TwoDatasBox(d1: d1, d2: d2)
94112
return box
95113
})
96-
114+
115+
Benchmark("DataIdenticalLarge", closure: { benchmark, box in
116+
blackHole(box.d1.isIdentical(to: box.d2))
117+
}, setup: { () -> TwoDatasBox in
118+
let d1 = createSomeData(1024 * 8)
119+
let d2 = d1
120+
let box = TwoDatasBox(d1: d1, d2: d2)
121+
return box
122+
})
123+
97124
Benchmark("DataNotEqualLarge", closure: { benchmark, box in
98125
blackHole(box.d1 != box.d2)
99126
}, setup: { () -> TwoDatasBox in
@@ -112,6 +139,15 @@ let benchmarks = {
112139
return box
113140
})
114141

142+
Benchmark("DataIdenticalReallyLarge", closure: { benchmark, box in
143+
blackHole(box.d1.isIdentical(to: box.d2))
144+
}, setup: { () -> TwoDatasBox in
145+
let d1 = createSomeData(1024 * 1024 * 8)
146+
let d2 = d1
147+
let box = TwoDatasBox(d1: d1, d2: d2)
148+
return box
149+
})
150+
115151
Benchmark("DataNotEqualReallyLarge", closure: { benchmark, box in
116152
blackHole(box.d1 != box.d2)
117153
}, setup: { () -> TwoDatasBox in

Sources/FoundationEssentials/Data/Data.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,3 +2974,53 @@ extension Data : Codable {
29742974
}
29752975
}
29762976
}
2977+
2978+
extension Data {
2979+
/// Returns a boolean value indicating whether this data is identical to
2980+
/// `other`.
2981+
///
2982+
/// Two data values are identical if there is no way to distinguish between
2983+
/// them.
2984+
///
2985+
/// Comparing data this way includes comparing (normally) hidden
2986+
/// implementation details such as the memory location of any underlying
2987+
/// data storage object. Therefore, identical data are guaranteed to
2988+
/// compare equal with `==`, but not all equal data are considered
2989+
/// identical.
2990+
///
2991+
/// - Performance: O(1)
2992+
@_alwaysEmitIntoClient
2993+
public func isIdentical(to other: Self) -> Bool {
2994+
// See if both are empty
2995+
switch (self._representation, other._representation) {
2996+
case (.empty, .empty):
2997+
return true
2998+
case (.inline, .inline), (.slice, .slice), (.large, .large):
2999+
// Continue on to checks below
3000+
break
3001+
default:
3002+
return false
3003+
}
3004+
3005+
let length1 = self.count
3006+
let length2 = other.count
3007+
3008+
// Unequal length data can never be equal
3009+
guard length1 == length2 else {
3010+
return false
3011+
}
3012+
3013+
if length1 > 0 {
3014+
return self.withUnsafeBytes { (b1: UnsafeRawBufferPointer) in
3015+
return other.withUnsafeBytes { (b2: UnsafeRawBufferPointer) in
3016+
// If they have the same base address and same count, it is equal
3017+
let b1Address = b1.baseAddress!
3018+
let b2Address = b2.baseAddress!
3019+
3020+
return b1Address == b2Address
3021+
}
3022+
}
3023+
}
3024+
return true
3025+
}
3026+
}

Tests/FoundationEssentialsTests/DataTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ extension Data {
4646
}
4747
}
4848

49+
func createSomeData(_ length: Int) -> Data {
50+
var d = Data(repeating: 42, count: length)
51+
// Set a byte to be another value just so we know we have a unique pointer to the backing
52+
// For maximum inefficiency in the not equal case, set the last byte
53+
d[length - 1] = UInt8.random(in: UInt8.min..<UInt8.max)
54+
return d
55+
}
56+
4957
@Suite("Data")
5058
private final class DataTests {
5159

@@ -197,6 +205,16 @@ private final class DataTests {
197205
#expect(d1 == d2, "Data should be equal")
198206
}
199207

208+
@Test func identical() {
209+
let d1 = createSomeData(1024 * 8)
210+
let d2 = createSomeData(1024 * 8)
211+
212+
#expect(d1.isIdentical(to: d1), "Data should be identical")
213+
#expect(d2.isIdentical(to: d2), "Data should be identical")
214+
#expect(!(d1.isIdentical(to: d2)), "Data should be identical")
215+
#expect(!(d2.isIdentical(to: d1)), "Data should be identical")
216+
}
217+
200218
@Test func dataInSet() {
201219
let d1 = dataFrom("Hello")
202220
let d2 = dataFrom("Hello")

0 commit comments

Comments
 (0)