Copy on Write for Structs and Enum of small size

I understand the copy on write when a struct or enum is large and it has to be stored in Heap via Value Witness Table. What if Struct or Enum is really small and can fit in InlineValueBuffer. When you assign that struct or enum to a different variable, it will be automatically written into a different existential container's InlineValueBuffer. In this scenario, it's not copied on write, is it copy on assign?

var s1 = SmallStruct(value: 1) //creates small object that can fit in Inline Value Buffer.
var s2 = s1 //create a new existensial container for s2 with same value stored in new container's Buffer.

Also,

  • Since in Swift4.2, String size is 16 bytes. Does that mean Strings are always stored in the heap even if they are smaller?
  • When a struct or enum is stored in the heap, it needs only one word of the 3 words inline value buffer. Does it mean other two words are wasted memory?

When you're working with values of struct type, there is never any implicit buffer allocation or copy-on-write. That's only a consideration if you're using protocol types. Struct and enum values are stored directly in memory that fits the size of the type. Now, in your example, if you had SmallStruct conform to a protocol, and s1 and s2 were of that protocol's type, it would behave as you said, where the value is stored inline in the value buffer and copied entirely when the value is copied:

protocol P {}
extension SmallStruct: P {}

var s1: P = SmallStruct(...) // SmallStruct value fits inside the buffer for P
var s2 = s1 // and is fully copied into the buffer of this other P

String will store small strings inline as well, but that's explicit in String's implementation, nothing magical about how Swift works.

2 Likes

@Joe_Groff Not sure what you mean by this. Structs and Enums don't use copy on write when they are referenced using variables of its own kind.

protocol P {}
extension SmallStruct: P {}

var s1: SmallStruct = SmallStruct(...) //s1 is of type SmallStruct.
var s2 = s1 // s2 is also of kind SmallStruct.

Where are s1 and s2 properties are stored?
If they are stored in Inline buffer, are they automatically copied to s2's inline when we assign s1 to s2?

protocol P {}
extension LargeStruct: P {}

var s1: SmallStruct = LargeStruct(...) //s1 is of type LargeStruct.
var s2 = s1 // s2 is also of kind LargeStruct.
  • Where are s1 and s2 properties are stored?
  • If they are stored in the heap since it can't fit in Inline buffer, they will be using copy on write or not?
  • if they are stored in the heap, they will be wasting 2 words in Inline Value Buffer?

In this case (before optimizations), s1 and s2 would each refer to a piece of memory the size of exactly one SmallStruct with no existential containers at all, since the compiler knows their exact type. The last line would copy the contents of s1 into s2 (copy on assign).

This would fail to compile since a LargeStruct is not a SmallStruct. Assuming you instead wrote var s1: LargeStruct = LargeStruct(...), s1 and s2 would each refer to a separate piece of memory that was the size of exactly one LargeStruct. Copying from s1 to s2 would copy the contents of s1 into s2 (copy on assign). There isn't any inline buffer to not fit into, since they aren't using existential containers at all.

If on the other hand you did this:

protocol P {}
extension LargeStruct: P {}

var s1: P = LargeStruct(...) // LargeStruct value does not fit inside the buffer for P and is allocated on the heap
var s2 = s1 // s2 takes a second reference to the heap-allocated buffer

then LargeStruct would be allocated into a heap value since s1 is now an existential container. The copy from s1 into s2 would copy the heap pointer into s2 and increment its reference count without copying what it points to unless you tried to modify either s1 or s2 (copy on write). It would waste two words in the Inline Value Buffer, and would waste more than two words in the data used to track the memory on the heap and the number of references to it.

1 Like