Which Value Types in Swift Supports Copy-On-Write

Which value types in Swift supports copy-on-write?

Copy-on write is supported for String and all collection types - Array, Dictionary and Set.

Besides that, compiler is free to optimize any struct access and effectively give you copy-on-write semantics, but it is not guaranteed.

Does swift copy on write for all structs?

Array is implemented with copy-on-write behaviour – you'll get it regardless of any compiler optimisations (although of course, optimisations can decrease the number of cases where a copy needs to happen).

At a basic level, Array is just a structure that holds a reference to a heap-allocated buffer containing the elements – therefore multiple Array instances can reference the same buffer. When you come to mutate a given array instance, the implementation will check if the buffer is uniquely referenced, and if so, mutate it directly. Otherwise, the array will perform a copy of the underlying buffer in order to preserve value semantics.

However, with your Point structure – you're not implementing copy-on-write at a language level. Of course, as @Alexander says, this doesn't stop the compiler from performing all sorts of optimisations to minimise the cost of copying whole structures about. These optimisations needn't follow the exact behaviour of copy-on-write though – the compiler is simply free to do whatever it wishes, as long as the program runs according to the language specification.

In your specific example, both p1 and p2 are global, therefore the compiler needs to make them distinct instances, as other .swift files in the same module have access to them (although this could potentially be optimised away with whole-module optimisation). However, the compiler still doesn't need to copy the instances – it can just evaluate the floating-point addition at compile-time and initialise one of the globals with 0.0, and the other with 1.0.

And if they were local variables in a function, for example:

struct Point {
var x: Float = 0
}

func foo() {
var p1 = Point()
var p2 = p1
p2.x += 1
print(p2.x)
}

foo()

The compiler doesn't even have to create two Point instances to begin with – it can just create a single floating-point local variable initialised to 1.0, and print that.

Regarding passing value types as function arguments, for large enough types and (in the case of structures) functions that utilise enough of their properties, the compiler can pass them by reference rather than copying. The callee can then make a copy of them only if needed, such as when needing to work with a mutable copy.

In other cases where structures are passed by value, it's also possible for the compiler to specialise functions in order to only copy across the properties that the function needs.

For the following code:

struct Point {
var x: Float = 0
var y: Float = 1
}

func foo(p: Point) {
print(p.x)
}

var p1 = Point()
foo(p: p1)

Assuming foo(p:) isn't inlined by the compiler (it will in this example, but once its implementation reaches a certain size, the compiler won't think it worth it) – the compiler can specialise the function as:

func foo(px: Float) {
print(px)
}

foo(px: 0)

It only passes the value of Point's x property into the function, thereby saving the cost of copying the y property.

So the compiler will do whatever it can in order to reduce the copying of value types. But with so many various optimisations in different circumstances, you cannot simply boil the optimised behaviour of arbitrary value types down to just copy-on-write.

How to prove copy-on-write on String type in Swift

The compiler creates only a single storage for both
"Hello World" and "Hello" + " World".

You can verify that for example by examining the assembly code
obtained from


swiftc -emit-assembly cow.swift

which defines only a single string literal


.section __TEXT,__cstring,cstring_literals
L___unnamed_1:
.asciz "Hello World"

As soon as the string is mutated, the address of the string storage
buffer (the first member of that "magic" tuple, actually _baseAddress
of struct _StringCore, defined in StringCore.swift) changes:

var xString = "Hello World"
var yString = "Hello" + " World"

print(_rawIdentifier(s: xString)) // (4300325536, 0)
print(_rawIdentifier(s: yString)) // (4300325536, 0)

yString.append("!")
print(_rawIdentifier(s: yString)) // (4322384560, 4322384528)

And why does your

func address(of object: UnsafeRawPointer) -> String

function show the same values for xArray and yArray, but
not for xString and yString?

Passing an array to a function taking a unsafe pointer passes the
address of the first array element, that is the same for both
arrays if they share the storage.

Passing a string to a function taking an unsafe pointer passes a
pointer to a temporary UTF-8 representation of the string.
That address can be different in each call, even for the same string.

This behavior is documented in the "Using Swift with Cocoa and
Objective-C" reference for UnsafePointer<T> arguments, but apparently
works the same for UnsafeRawPointer arguments.

Anomaly in CoW (Copy on Write) of Swift Array with Reference type items

Looks like UnsafePointer(&value) returns the wrong value (maybe it is the head of the array or something like this). I changed the someFunc a little bit.

func someFunc(array: [TestClass]) {
var anotherArray = array
anotherArray.append(TestClass(name: "mnop"))

for var value in array {
debugPrint(Unmanaged.passUnretained(value).toOpaque())
}

for var value in anotherArray {
debugPrint(Unmanaged.passUnretained(value).toOpaque())
}

anotherArray[0].name = "Sandeep"
debugPrint(array[0].name)
}

And the output is the following:

0x0000600003f29360
0x0000600003f29380
0x0000600003f29400

0x0000600003f29360
0x0000600003f29380
0x0000600003f29400
0x0000600003f29340

As you can see, both arrays contain the same objects, and this is the expected behavior. Array stores references to TestClass objects (not values) and copies these references during CoW, but the objects remain the same.

Are swift classes inside struct passed by copy during assignment?

Passed by reference. You can test it. Declare:

class A{}
struct B { let a = A()}

then:

let b = B()
print("A = \(unsafeAddressOf(b.a))")//0x0000600000019450
let b_copy = b
print("A = \(unsafeAddressOf(b_copy.a))")//0x0000600000019450

Apple's description of reference and value types with multiple threads

As others have pointed out, reference types always pass a pointer to the object, which is ideal where you want a "shared, mutable state" (as that document you referenced said). Clearly, though, if you're mutating/accessing a reference type across multiple threads, make sure to synchronize your access to it (via a dedicated serial queue, the reader-writer pattern, locks, etc.).

Value types are a little more complicated, though. Yes, as the others have pointed out, if you pass a value type as a parameter to a method that then does something on another thread, you're essentially working with a copy of that value type (Josh's note regarding the copy-on-write, notwithstanding). This ensures the integrity of that object passed to the method. That's fine (and has been sufficiently covered by the other answers here).

But it gets more complicated when you are dealing with closures. Consider, for example, the following:

struct Person {
var firstName: String
var lastName: String
}

var person = Person(firstName: "Rob", lastName: "Ryan")

DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 1)
print("1: \(person)")
}

person.firstName = "Rachel"
Thread.sleep(forTimeInterval: 2)
person.lastName = "Moore"
print("2: \(person)")

Obviously, you wouldn't generally sleep, but I'm doing this to illustrate the point: Namely, even though we're dealing with a value type and multiple threads, the person you reference in the closure is the same instance as you're dealing with on the main thread (or whatever thread this was running on), not a copy of it. If you're dealing with a mutable object, that's not thread-safe.

I've contrived this example to illustrate this point, where the print statement inside the closure above will report "Rachel Ryan", effectively showing the state of the Person value type in an inconsistent state.

With closures using value types, if you want to enjoy value semantics, you have to change that async call to use a separate variable:

let separatePerson = person
queue.async {
Thread.sleep(forTimeInterval: 1)
print("1: \(separatePerson)")
}

Or, even easier, use a "capture list", which indicates what value type variables should be captured by the closure:

queue.async { [person] in
Thread.sleep(forTimeInterval: 1)
print("1: \(person)")
}

With either of these examples, you're now enjoying value semantics, copying the object, and the print statement will correctly report "Rob Ryan" even though the original person object is being mutated on another thread.

So, if you are dealing with value types and closures, value types can be shared across threads unless you explicitly use capture list (or something equivalent) in order to enjoy value semantics (i.e. copying the object as needed).



Related Topics



Leave a reply



Submit