Skip to content

Instantly share code, notes, and snippets.

@jaredkhan
Last active April 5, 2021 15:06
Show Gist options
  • Save jaredkhan/5c77a7f79c6adcdf58c9a08c9d53a023 to your computer and use it in GitHub Desktop.
Save jaredkhan/5c77a7f79c6adcdf58c9a08c9d53a023 to your computer and use it in GitHub Desktop.
A few demonstrations of the copy-on-write behaviour of containers in the Swift Standard Library.
/// A few demonstrations of the copy-on-write behaviour of containers in the Swift Standard Library.
/// https://jaredkhan.com/blog/swift-copy-on-write
/// Pause execution so that we can look at memory usage in Activity Monitor or similar.
func pause(expectedMemoryUsage: Int) {
print("Expecting memory usage of about \(expectedMemoryUsage)MB. Press [enter] to continue")
let _ = readLine()
}
/// Demonstrate the copy-on-write behaviour of a large array.
func largeArray() {
print("Demoing copy-on-write of a large array")
pause(expectedMemoryUsage: 0)
let x = Array(repeating: Int64(1), count: 100_000_000)
// 100M 8-byte integers should result in around 800MB of memory usage.
// You can check this in Activity Monitor or a similar program.
pause(expectedMemoryUsage: 800)
var y = x
// Both x and y have a reference to the same buffer.
pause(expectedMemoryUsage: 800)
y.append(2)
// Have made a mutating whilst both x and y are referencing the buffer.
// The buffer will be copied first, so expect higher memory usage.
pause(expectedMemoryUsage: 1600)
}
/// Demonstrate the copy-on-write behaviour of an array within an array.
func nestedArray() {
print("Demoing copy-on-write of a nested array")
pause(expectedMemoryUsage: 0)
let x = [
Array(repeating: Int64(1), count: 100_000_000),
Array(repeating: Int64(1), count: 100_000_000),
]
// x's buffer doesn't itself hold a lot of data,
// rather its elements hold references to large buffers
pause(expectedMemoryUsage: 1600)
var y = x
y.append([])
// Have made a mutation to y's buffer, but not to the large buffers of the elements
pause(expectedMemoryUsage: 1600)
y[0].append(2)
// Have mad e mutation to one of the large buffers, so that one buffer will be copied first
pause(expectedMemoryUsage: 2400)
}
func arrayInStruct() {
print("Demoing copy-on-write of an array within a struct")
pause(expectedMemoryUsage: 0)
struct ThingWithArray {
var name: String
var array: [Int64]
}
let x = ThingWithArray(
name: "Nice Thing",
array: Array(repeating: Int64(1), count: 100_000_000)
)
pause(expectedMemoryUsage: 800)
var y = x
// y is now a copy of x, but has a reference to the same array buffer
pause(expectedMemoryUsage: 800)
y.name = "Nicer Thing"
// mutating y does not touch the array buffer, so no large data is copied
pause(expectedMemoryUsage: 800)
y.array.append(1)
// now attempting to mutate the array buffer whilst reference count is >1,
// so a copy is taken
pause(expectedMemoryUsage: 1600)
}
func manyStructs() {
print("Demoing that structs don't do copy-on-write")
struct Thing {
// A struct with about 80 bytes
let a: Int64 = 0
let b: Int64 = 0
let c: Int64 = 0
let d: Int64 = 0
let e: Int64 = 0
let f: Int64 = 0
let g: Int64 = 0
let h: Int64 = 0
let i: Int64 = 0
let j: Int64 = 0
}
let thing = Thing()
let x = Array(repeating: thing, count: 10_000_000)
// Structs themselves do not have copy-on-write semantics,
// so expect this struct to have really been copied 1M times
pause(expectedMemoryUsage: 800)
}
func mutateWithOneReference() {
print("Demoing no copy when uniquely referenced")
pause(expectedMemoryUsage: 0)
var x = Array(repeating: Int64(1), count: 100_000_000)
pause(expectedMemoryUsage: 800)
({
let y = x
pause(expectedMemoryUsage: 800)
}())
// y has now gone out of scope so x's buffer's reference count = 1
x.append(1)
pause(expectedMemoryUsage: 800)
}
largeArray()
nestedArray()
arrayInStruct()
manyStructs()
mutateWithOneReference()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment