I generally use a lot of UnsafeBufferPointer<Element>
for “performance” and really only return Swift native Array
s for user-facing functions. However I’ve been thinking a lot about this given the recent horror stories going around and I’m wondering if Array
s are actually friendlier to the Swift optimizer and I should prefer using them for reasons other than following Swift idioms. I can think of a couple reasons this might be the case:
Pro:
-
If
Foundation
is out of the picture,Array
s have basically the same amount of indirection asBuffer
s, with the slight difference thatArrays
store their count and capacity inline in their backing storage, and so take up just one word on the stack. (While an equivalent vectorbuffer takes three.) -
Swift
Array
s have value semantics and their contents obeylet
andvar
making it easier for the optimizer to reason about them. -
Array
s check the element indices and trap on out-of-bounds. I’ve written enough C and C++ to appreciate this feature and the penalty seems to be like less than 10% at worst. And it can always be circumvented usingwithUnsafeBufferPointer
. -
Arrays
lend themselves better to functional styles of programming and so are easier vectorize and parallelize. -
Memory-safe
Buffer
s need to be wrapped inclass
types (because they needdeinit
) when facing users, which effectively adds a layer of storage indirection. AnArray
can be safely placed inside astruct
type when designing APIs. (a workaround: can use unmanaged buffer with header class type,, but at that point you’re basically rewritingArray
.)
Con:
-
Array
s have reference counting overhead. I don’t know if the compiler knows to optimize this away if theArray
is local and never gets returned. -
Swift doesn’t have a concept of a fixed-size array (not talking about stack-allocated arrays or contiguous tuples but certainly related). This means the count and capacity are redundant in these cases and I’m not sure the compiler can optimize based on knowledge the array never changes size the way a buffer pointer never changes size.
-
Array
s have value semantics and so you can’t opt-into shared, manually-managed storage the way you can with aBuffer
. This meansArray
s accumulate a lot of reference counting overhead if they move around enough, even if they never get mutated. -
If you’re using
Foundation
, then you have to remember to useContiguousArray
and notArray
(ugh), and on top of that, weird things happen performance-wise when you useContiguousArray
. Though this is only really relevant if you’re someone who uses a lot ofclass
types.
Can someone who knows more about the Swift optimizer comment on this?