3

I've read about Copy-on-Write concept for optimization in Arrays and other data structures in Swift.

What I want to know is how Copy-on-Write works in a multi-threaded environment.

    let arr1 = [1, 2, 3, 4]
    let arr2 = arr1
    arr1.withUnsafeBytes { print("arr1:", $0.baseAddress) } //0x000060000007ee60
    arr2.withUnsafeBytes { print("arr2:", $0.baseAddress) } //0x000060000007ee60
    
    DispatchQueue.global(qos: .default).async {
        let arr3 = arr1
        arr3.withUnsafeBytes { print("arr3:", $0.baseAddress) } //0x000060000007ee60
    }

In the above code, arr1 and arr2 have same addresses initially as expected in copy-on-write. But, arr3 also shares the same memory as arr1 and arr2 although it is executed on a different thread.

As far as I know, each thread has different stack allocation. Then why arr3 is still sharing the same location?

halfer
  • 19,824
  • 17
  • 99
  • 186
PGDev
  • 23,751
  • 6
  • 34
  • 88
  • 5
    `let arr3 = arr1` makes both arrays (initially) share element storage – why should it matter on which thread that code is executed? Btw, the element storage is allocated memory, i.e. on the heap, not on the stack. – Martin R Jun 19 '18 at 06:52
  • @MartinR Doesn't element storage depends on what kind of element it is? – PGDev Jun 19 '18 at 06:57
  • 2
    I am not sure what you mean and how that is related to the question. Array is a (fixed-sized) *struct* with a pointer to the (allocated) element storage. – Have a look at https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html where an Array type with COW is "re-invented," that might give some insights. – Martin R Jun 19 '18 at 07:03

1 Answers1

5

You're not looking at the addresses of the arrays. You're looking at the addresses of the internal backing storage of the arrays, which is shared and heap-allocated.

If you want to look at the addresses of the stack-allocated array container (the part that points to the backing storage), then you meant this:

var arr1 = [1, 2, 3, 4]
var arr2 = arr1
withUnsafePointer(to: &arr1) { print("arr1:", $0) }
withUnsafePointer(to: &arr2) { print("arr2:", $0) }

DispatchQueue.global(qos: .default).async {
    let arr3 = arr1
    withUnsafePointer(to: arr3) { print("arr3:", $0) }
}

// =>
arr1: 0x0000000122d671e0   // local stack
arr2: 0x0000000122d671e8   // local stack (next address)
arr3: 0x0000700000e48d10   // heap

I believe this is the kind of result you were expecting.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610