Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Swift copy on mutation in this scenario?

Tags:

memory

swift

Essentially what I want is a temporary alias for a class property to improve readability.

I'm in the situation described by the following code and I can't see a straightforward solution. What I want to avoid is y being copied on mutation and then copied back. Renaming y would reduce the readability of the actual algorithm a lot.

Is the Swift compiler smart enough to not actually allocate new memory and how would I be able to know that?

If not, how to prevent copying?

class myClass {
    var propertyWithLongDescriptiveName: [Float]

    func foo() {
        var y = propertyWithLongDescriptiveName
        // mutate y with formulas where y corresponds to a `y` from some paper
        // ...
        propertyWithLongDescriptiveName = y
    }
    // ...
}
like image 590
Frederick Squid Avatar asked Dec 19 '16 18:12

Frederick Squid


2 Answers

struct Array is a value types in Swift, which means that they are always copied when assigned to another variable. However, each struct Array contains pointers (not visible in the public interface) to the actual element storage. Therefore after

var a = [1, 2, 3, 4]
var b = a

both a and b are (formally independent) values, but with pointers to the same element storage. Only when one of them is mutated, a copy of the element storage is made. This is called "copy on write" and for example explained in

  • Friday Q&A 2015-04-17: Let's Build Swift.Array

So after

b[0] = 17

a and b are values with pointers to different (independent) element storage. Further mutation of b does not copy the element storage again (unless b is copied to another variable). Finally, if you assign the value back

a = b

the old element storage of a is released, and both values are pointers to the same storage again.

Therefore in your example:

    var y = propertyWithLongDescriptiveName
    // ... mutate y ...
    propertyWithLongDescriptiveName = y

a copy of the element storage is made exactly once (assuming that you don't copy y to an additional variable).

If the array size does not change then a possible approach could be

var propertyWithLongDescriptiveName = [1.0, 2.0, 3.0, 4.0]

propertyWithLongDescriptiveName.withUnsafeMutableBufferPointer { y in
    // ... mutate y ...
    y[0] = 13
}

print(propertyWithLongDescriptiveName) // [13.0, 2.0, 3.0, 4.0]

withUnsafeMutableBufferPointer() calls the closure with an UnsafeMutableBufferPointer to the element storage. A UnsafeMutableBufferPointer is a RandomAccessCollection and therefore offers an array-like interface.

like image 52
Martin R Avatar answered Nov 03 '22 14:11

Martin R


No, the Swift compiler is not that smart. All you need is a small test to see what it does:

class MyClass {
    var propertyWithLongDescriptiveName: [Float] = [1,2]

    func foo() {
        var y = propertyWithLongDescriptiveName
        y[0] = 3 // copied an mutated

        print(y)                               // [3,2]
        print(propertyWithLongDescriptiveName) // [1,2]
    }
}

let mc = MyClass()
mc.foo()

You have 2 optons:

  • Change propertyWithLongDescriptiveName to NSMutableArray, which is a reference type
  • Accept the overhead cost of copy-and-mutate to trade for readability of your algorithm. In many cases memory allocation cost is minimal compared to your algorithm's.
like image 2
Code Different Avatar answered Nov 03 '22 14:11

Code Different