Compare mutable buffers for equality

Compare mutable buffers for equality

In the example below, I have two approaches that compare mutable buffers for equality. One approach uses memcmp which returns 0 if the buffers are considered equal. The other approach casts the buffers to arrays and compares the arrays for equality. Which is the preferred approach here, especially for mutable buffers that contain thousands of elements?

import Foundation

let n = 5

let sa = UnsafeMutablePointer<Double>.allocate(capacity: n)
let a = UnsafeMutableBufferPointer(start: sa, count: n)
a.initialize(repeating: 1.5)

let sb = UnsafeMutablePointer<Double>.allocate(capacity: n)
let b = UnsafeMutableBufferPointer(start: sb, count: n)
b.initialize(repeating: 1.5)
b[n - 1] = 9

let result1 = memcmp(a.baseAddress, b.baseAddress, MemoryLayout<Double>.size * n)
let result2 = Array(a) == Array(b)


This prints the following:

[1.5, 1.5, 1.5, 1.5, 1.5]
[1.5, 1.5, 1.5, 1.5, 9.0]

If you just want a simple yes/no use memcmp because it avoid two extra allocations that the Array(a) and Array(b) perform which could be pretty expensive especially considering the fact that you mentioned:

contain thousands of elements


Converting to Array is definitely not idiomatic. The most idiomatic Swift option here is to use a.elementsEqual(b), but memcmp will be somewhat more efficient in general (ideally there'd be no performance difference, but achieving that is a little bit subtle).


I did a timing test and elementsEqual is faster than memcmp for buffers with less than 4,000 elements (at least on my MacBook Pro). So memcmp is definitely better for comparing very large buffers but elementsEqual is better for smaller buffers. Any advice on how to determine this cut-off point?

Just to be awful, I'll point out that if you're comparing Doubles specifically, memcmp and elementsEqual/== won't do the same thing, because Double.nan != .nan.


And if you are not comparing doubles specifically beware of padding:

struct S {
   var double: Double
   var int32: Int32 
} // size = 64+32, stride = 64 + 64

in which case memcmp might give incorrect results if there's the padding bytes are different in the things being compared (although I don't know an easy way of making them different).


Also -0 == +0, but they have different bit patterns.

Even if you're just comparing integers, though, memcmp has subtly different semantics--it requires that the buffers are the same size, and is allowed to access the buffers past the first mismatch, while elementsEqual stops as soon as it hits elements that do not match and does not require that their sizes match. It's somewhat hard to construct cases where this distinction matters, but it makes it very hard to tell the optimizer that it can perform this transformation.