diff --git a/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift b/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift index fcc25aab8..fc5ef7e55 100644 --- a/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift +++ b/Benchmarks/Benchmarks/Essentials/BenchmarkEssentials.swift @@ -34,4 +34,91 @@ let benchmarks = { assert(u1 != u2) } } + + // MARK: 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.. 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 + 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) + }, setup: { () -> TwoDatasBox in + let d1 = createSomeData(12) // Less than size of InlineData.Buffer + let d2 = createSomeData(12) + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + + Benchmark("DataEqualLarge", closure: { benchmark, box in + blackHole(box.d1 == 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 + let d1 = createSomeData(1024 * 8) + let d2 = createSomeData(1024 * 8) + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + + Benchmark("DataEqualReallyLarge", closure: { benchmark, box in + blackHole(box.d1 == 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 + let d1 = createSomeData(1024 * 1024 * 8) + let d2 = createSomeData(1024 * 1024 * 8) + let box = TwoDatasBox(d1: d1, d2: d2) + return box + }) + } diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 178c7cfa3..9f5bb7245 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2709,14 +2709,36 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect /// Returns `true` if the two `Data` arguments are equal. @inlinable // This is @inlinable as emission into clients is safe -- the concept of equality on Data will not change. public static func ==(d1 : Data, d2 : Data) -> Bool { + // See if both are empty + switch (d1._representation, d2._representation) { + case (.empty, .empty): + return true + default: + // Continue on to checks below + break + } + let length1 = d1.count - if length1 != d2.count { + let length2 = d2.count + + // Unequal length data can never be equal + guard length1 == length2 else { return false } + if length1 > 0 { return d1.withUnsafeBytes { (b1: UnsafeRawBufferPointer) in return d2.withUnsafeBytes { (b2: UnsafeRawBufferPointer) in - return memcmp(b1.baseAddress!, b2.baseAddress!, b2.count) == 0 + // If they have the same base address and same count, it is equal + let b1Address = b1.baseAddress! + let b2Address = b2.baseAddress! + + guard b1Address != b2Address else { + return true + } + + // Compare the contents + return memcmp(b1Address, b2Address, b2.count) == 0 } } }