diff --git a/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift b/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift index fc5ef7e55..14e2c7289 100644 --- a/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift +++ b/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift @@ -67,6 +67,15 @@ let benchmarks = { return box }) + Benchmark("DataIdenticalEmpty", closure: { benchmark, box in + blackHole(box.d1.isIdentical(to: box.d2)) + }, setup: { () -> TwoDatasBox in + let d1 = Data() + let d2 = d1 + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + Benchmark("DataEqualInline", closure: { benchmark, box in blackHole(box.d1 == box.d2) }, setup: { () -> TwoDatasBox in @@ -75,6 +84,15 @@ let benchmarks = { let box = TwoDatasBox(d1: d1, d2: d2) return box }) + + Benchmark("DataIdenticalInline", closure: { benchmark, box in + blackHole(box.d1.isIdentical(to: box.d2)) + }, setup: { () -> TwoDatasBox in + let d1 = createSomeData(12) // Less than size of InlineData.Buffer + let d2 = d1 + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) Benchmark("DataNotEqualInline", closure: { benchmark, box in blackHole(box.d1 != box.d2) @@ -93,7 +111,16 @@ let benchmarks = { let box = TwoDatasBox(d1: d1, d2: d2) return box }) - + + Benchmark("DataIdenticalLarge", closure: { benchmark, box in + blackHole(box.d1.isIdentical(to: box.d2)) + }, setup: { () -> TwoDatasBox in + let d1 = createSomeData(1024 * 8) + let d2 = d1 + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + Benchmark("DataNotEqualLarge", closure: { benchmark, box in blackHole(box.d1 != box.d2) }, setup: { () -> TwoDatasBox in @@ -112,6 +139,15 @@ let benchmarks = { return box }) + Benchmark("DataIdenticalReallyLarge", closure: { benchmark, box in + blackHole(box.d1.isIdentical(to: box.d2)) + }, setup: { () -> TwoDatasBox in + let d1 = createSomeData(1024 * 1024 * 8) + let d2 = d1 + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + Benchmark("DataNotEqualReallyLarge", closure: { benchmark, box in blackHole(box.d1 != box.d2) }, setup: { () -> TwoDatasBox in diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index ecea12041..cb96088cc 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2974,3 +2974,53 @@ extension Data : Codable { } } } + +extension Data { + /// Returns a boolean value indicating whether this data is identical to + /// `other`. + /// + /// Two data values are identical if there is no way to distinguish between + /// them. + /// + /// Comparing data this way includes comparing (normally) hidden + /// implementation details such as the memory location of any underlying + /// data storage object. Therefore, identical data are guaranteed to + /// compare equal with `==`, but not all equal data are considered + /// identical. + /// + /// - Performance: O(1) + @_alwaysEmitIntoClient + public func isIdentical(to other: Self) -> Bool { + // See if both are empty + switch (self._representation, other._representation) { + case (.empty, .empty): + return true + case (.inline, .inline), (.slice, .slice), (.large, .large): + // Continue on to checks below + break + default: + return false + } + + let length1 = self.count + let length2 = other.count + + // Unequal length data can never be equal + guard length1 == length2 else { + return false + } + + if length1 > 0 { + return self.withUnsafeBytes { (b1: UnsafeRawBufferPointer) in + return other.withUnsafeBytes { (b2: UnsafeRawBufferPointer) in + // If they have the same base address and same count, it is equal + let b1Address = b1.baseAddress! + let b2Address = b2.baseAddress! + + return b1Address == b2Address + } + } + } + return true + } +} diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 3b5798b25..69a63eb42 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -46,6 +46,14 @@ extension Data { } } +func createSomeData(_ length: Int) -> Data { + var d = Data(repeating: 42, count: length) + // Set a byte to be another value just so we know we have a unique pointer to the backing + // For maximum inefficiency in the not equal case, set the last byte + d[length - 1] = UInt8.random(in: UInt8.min..