1

I've noticed that when creating an array of any of the fundamental types, given a sizeof(..) <= 16 bytes (Bool, Double, Float and some Int types tested, see below), then Swift seemingly always pre-allocates memory in multiples of 16 bytes by default. Also, when using the .reserveCapacity(..) array method; even if a multiple of 8 bytes would suffice, Swift still allocates space in multiples of 16 bytes.

Question: Out of curiosity, what is the reason for Swift (compiler) doing like this? Or is it possibly something that doesn't specifically depend on Swift, but rather the compiler used by Swift, or my processor?


Example

Default:

/* Memory footprint */
sizeof(().dynamicType) // 0
sizeof(Bool) // 1
sizeof(Int16) // 2
sizeof(Int32) // 4
sizeof(Int64) // 8
sizeof(Float) // 4
sizeof(Double) // 8
sizeof(String) // 24 <-- for this, in multiples of 24 bytes, OK

/* Default pre-allocation in multiples of 16 bytes? */
let boolArr : [Bool] = [true]
let boolArrExact = [Bool](count: 16, repeatedValue: true)
let boolArrNext = [Bool](count: 17, repeatedValue: true)
let boolArrNextNext = [Bool](count: 33, repeatedValue: true)
boolArr.capacity // 16 elements <=> 16 bytes
boolArrExact.capacity // 16 elements <=> 16 bytes
boolArrNext.capacity // 32 elements <=> 32 bytes
boolArrNextNext.capacity // 48 elements <=> 48 bytes

let int16arr = [Int16](count: 1, repeatedValue: 1)
let int16arrExact = [Int16](count: 8, repeatedValue: 1)
let int16arrNext = [Int16](count: 9, repeatedValue: 1)
int16arr.capacity // 8 elements <=> 2*8 = 16 bytes
int16arrExact.capacity // 8 elements <=> 2*8 = 16 bytes
int16arrNext.capacity // 16 elements <=> 2*(2*8) = 32 bytes

let int64arr = [Int64](count: 1, repeatedValue: 1)
let int64arrNext = [Int64](count: 3, repeatedValue: 1)
int64arr.capacity // 2 elements <=> 8*2 = 16 bytes
int64arrNext.capacity // 4 elements <=> 2*(8*2) = 32 bytes

Same behaviour using .reserveCapacity(...):

/* even when explicitly reserving capacity for less than default */
var boolArrCustomA : [Bool] = []
boolArrCustomA.reserveCapacity(5) // 8 bytes should suffice
boolArrCustomA.append(true)
boolArrCustomA.capacity // 16

var boolArrCustomB : [Bool] = []
boolArrCustomB.reserveCapacity(17) // 24 bytes should suffice
boolArrCustomB.append(true)
boolArrCustomB.capacity // 32

I'm running Swift 2.1.1, XCode 7.2, on a 64-bit Intel Core i5 machine.


Somewhat related threads that, however, have not provided me with an answer to this question:

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192

1 Answers1

3

All allocation on MacOS X and iOS is done with 16 byte alignment, not restricted to Swift, and this has been the case for many years. This gives some speed improvement because a single item is more likely to be contained in a single cache line, and some more speed improvements if vector operations are used. It simplifies the algorithms for allocation and deallocation. In addition, it allows "tagged pointer objects" because the last four bits in any pointer to an object are guaranteed to be zero.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • I see, thanks for your answer. I'll add for possible future visitors these relevant links (found after being given your explanation above) https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/libgmalloc.3.html http://stackoverflow.com/questions/612443/why-does-the-mac-abi-require-16-byte-stack-alignment-for-x86-32 This 16-byte alignment seems to have been used at least since Mac OS X 10.5. – dfrib Jan 05 '16 at 19:21