swift 是否对所有结构进行写时复制?
Does swift copy on write for all structs?
我知道 swift 会优化数组的写入时复制,但它会为所有结构执行此操作吗?例如:
struct Point {
var x:Float = 0
}
var p1 = Point()
var p2 = p1 //p1 and p2 share the same data under the hood
p2.x += 1 //p2 now has its own copy of the data
Array
是 实现的 具有写时复制行为 – 无论编译器优化如何,您都会得到它(当然,优化可以减少数量需要复制的情况)。
在基本层面上,Array
只是一个结构,它包含对包含元素的堆分配缓冲区的引用——因此多个 Array
实例可以引用相同的 缓冲区。当您要改变给定的数组实例时,实现将检查缓冲区是否被唯一引用,如果是,则直接改变它。否则,数组将执行底层缓冲区的副本以保留值语义。
但是,使用您的 Point
结构 – 您没有在语言级别实现写时复制。当然,作为 ,这不会阻止编译器执行各种优化以最小化复制整个结构的成本。不过,这些优化不需要完全遵循写时复制的行为——只要程序根据语言规范运行,编译器就可以随心所欲。
在您的特定示例中,p1
和 p2
都是全局的,因此编译器需要使它们成为不同的实例,因为同一模块中的其他 .swift 文件可以访问对他们来说(尽管这可以通过整个模块优化来优化)。然而,编译器仍然不需要复制实例——它可以只 evaluate the floating-point addition at compile-time 并用 0.0
初始化一个全局变量,另一个用 1.0
.
如果它们是函数中的局部变量,例如:
struct Point {
var x: Float = 0
}
func foo() {
var p1 = Point()
var p2 = p1
p2.x += 1
print(p2.x)
}
foo()
编译器甚至不必创建两个 Point
实例开始——它可以只创建一个初始化为 1.0
的浮点局部变量,然后打印它。
关于将值类型作为函数参数传递,对于足够大的类型和(在结构的情况下)充分利用其属性的函数,编译器 can pass them by reference 而不是复制。然后,被调用者可以仅在需要时制作它们的副本,例如需要使用可变副本时。
在按值传递结构的其他情况下,编译器也可以 specialise functions 以便仅复制函数需要的属性。
对于以下代码:
struct Point {
var x: Float = 0
var y: Float = 1
}
func foo(p: Point) {
print(p.x)
}
var p1 = Point()
foo(p: p1)
假设 foo(p:)
没有被编译器内联(在这个例子中它会内联,但是一旦它的实现达到一定的大小,编译器就不会认为它值得)——编译器可以专门化功能为:
func foo(px: Float) {
print(px)
}
foo(px: 0)
它只将Point
的x
属性的值传递给函数,从而节省了复制y
属性的开销。
因此编译器将竭尽所能减少值类型的复制。但是在不同情况下有这么多不同的优化,你不能简单地将任意值类型的优化行为归结为写时复制。
Swift牛
默认Value Type
[About]不支持COW(Copy on Write)机制。但是一些系统类像Collections
支持它
如何查看地址
// Print memory address
func address(_ object: UnsafeRawPointer) -> String {
let address = Int(bitPattern: object)
return NSString(format: "%p", address) as String
}
默认行为
struct A {
var value: Int = 0
}
//Default behavior(COW is not used)
var a1 = A()
var a2 = a1
//different addresses
print(address(&a1)) //0x7ffee48f24a8
print(address(&a2)) //0x7ffee48f24a0
//COW is not used
a2.value = 1
print(address(&a2)) //0x7ffee48f24a0
Collection 作为例外
//collection(COW is realized)
var collection1 = [A()]
var collection2 = collection1
//same addresses
print(address(&collection1)) //0x600000c2c0e0
print(address(&collection2)) //0x600000c2c0e0
//COW is used
collection2.append(A())
print(address(&collection2)) //0x600000c2c440
我们有下一个 问题 - 如果 value type
通过 reference type
有对堆的引用,则引用的值在不同 [=19= 之间共享]
//problem with reference to heap
class C {
var value: Int = 0
}
struct B {
var c: C
}
var b1 = B(c: C())
var b2 = b1
print(address(&b1)) //0x7ffeebd443d0
print(address(&b2)) //0x7ffeebd443c8
b2.c.value = 1
print(address(&b2)) //0x7ffeebd443c8
print(b1.c.value) //1 //<- is changed also
print(b2.c.value) //1
解决方案是使用某种self-written实现COW
//Adding COW
final class Ref<T> {
var value: T
init(value: T) {
self.value = value
}
}
struct Box<T> {
var reference: Ref<T>
init(interior: T) {
self.reference = Ref(value: interior)
}
var value: T {
get {
return reference.value
}
set {
//it is true when accessing throught first owner
//when `box1.value = 1` isKnownUniquelyReferenced returns true
if (isKnownUniquelyReferenced(&self.reference)) {
self.reference.value = newValue
} else {
self.reference = Ref(value: newValue)
}
}
}
}
var box1 = Box(interior: 0)
var box2 = box1
//same addresses
print(address(&box1)) //0x7ffee11b53d0
print(address(&box2)) //0x7ffee11b53c0
//COW is used
box2.value = 1
print(address(&box2)) //0x7ffee11b53c0
print(box1.value) //0 // <-
print(box2.value) //1
我知道 swift 会优化数组的写入时复制,但它会为所有结构执行此操作吗?例如:
struct Point {
var x:Float = 0
}
var p1 = Point()
var p2 = p1 //p1 and p2 share the same data under the hood
p2.x += 1 //p2 now has its own copy of the data
Array
是 实现的 具有写时复制行为 – 无论编译器优化如何,您都会得到它(当然,优化可以减少数量需要复制的情况)。
在基本层面上,Array
只是一个结构,它包含对包含元素的堆分配缓冲区的引用——因此多个 Array
实例可以引用相同的 缓冲区。当您要改变给定的数组实例时,实现将检查缓冲区是否被唯一引用,如果是,则直接改变它。否则,数组将执行底层缓冲区的副本以保留值语义。
但是,使用您的 Point
结构 – 您没有在语言级别实现写时复制。当然,作为
在您的特定示例中,p1
和 p2
都是全局的,因此编译器需要使它们成为不同的实例,因为同一模块中的其他 .swift 文件可以访问对他们来说(尽管这可以通过整个模块优化来优化)。然而,编译器仍然不需要复制实例——它可以只 evaluate the floating-point addition at compile-time 并用 0.0
初始化一个全局变量,另一个用 1.0
.
如果它们是函数中的局部变量,例如:
struct Point {
var x: Float = 0
}
func foo() {
var p1 = Point()
var p2 = p1
p2.x += 1
print(p2.x)
}
foo()
编译器甚至不必创建两个 Point
实例开始——它可以只创建一个初始化为 1.0
的浮点局部变量,然后打印它。
关于将值类型作为函数参数传递,对于足够大的类型和(在结构的情况下)充分利用其属性的函数,编译器 can pass them by reference 而不是复制。然后,被调用者可以仅在需要时制作它们的副本,例如需要使用可变副本时。
在按值传递结构的其他情况下,编译器也可以 specialise functions 以便仅复制函数需要的属性。
对于以下代码:
struct Point {
var x: Float = 0
var y: Float = 1
}
func foo(p: Point) {
print(p.x)
}
var p1 = Point()
foo(p: p1)
假设 foo(p:)
没有被编译器内联(在这个例子中它会内联,但是一旦它的实现达到一定的大小,编译器就不会认为它值得)——编译器可以专门化功能为:
func foo(px: Float) {
print(px)
}
foo(px: 0)
它只将Point
的x
属性的值传递给函数,从而节省了复制y
属性的开销。
因此编译器将竭尽所能减少值类型的复制。但是在不同情况下有这么多不同的优化,你不能简单地将任意值类型的优化行为归结为写时复制。
Swift牛
默认Value Type
[About]不支持COW(Copy on Write)机制。但是一些系统类像Collections
支持它
如何查看地址
// Print memory address
func address(_ object: UnsafeRawPointer) -> String {
let address = Int(bitPattern: object)
return NSString(format: "%p", address) as String
}
默认行为
struct A {
var value: Int = 0
}
//Default behavior(COW is not used)
var a1 = A()
var a2 = a1
//different addresses
print(address(&a1)) //0x7ffee48f24a8
print(address(&a2)) //0x7ffee48f24a0
//COW is not used
a2.value = 1
print(address(&a2)) //0x7ffee48f24a0
Collection 作为例外
//collection(COW is realized)
var collection1 = [A()]
var collection2 = collection1
//same addresses
print(address(&collection1)) //0x600000c2c0e0
print(address(&collection2)) //0x600000c2c0e0
//COW is used
collection2.append(A())
print(address(&collection2)) //0x600000c2c440
我们有下一个 问题 - 如果 value type
通过 reference type
有对堆的引用,则引用的值在不同 [=19= 之间共享]
//problem with reference to heap
class C {
var value: Int = 0
}
struct B {
var c: C
}
var b1 = B(c: C())
var b2 = b1
print(address(&b1)) //0x7ffeebd443d0
print(address(&b2)) //0x7ffeebd443c8
b2.c.value = 1
print(address(&b2)) //0x7ffeebd443c8
print(b1.c.value) //1 //<- is changed also
print(b2.c.value) //1
解决方案是使用某种self-written实现COW
//Adding COW
final class Ref<T> {
var value: T
init(value: T) {
self.value = value
}
}
struct Box<T> {
var reference: Ref<T>
init(interior: T) {
self.reference = Ref(value: interior)
}
var value: T {
get {
return reference.value
}
set {
//it is true when accessing throught first owner
//when `box1.value = 1` isKnownUniquelyReferenced returns true
if (isKnownUniquelyReferenced(&self.reference)) {
self.reference.value = newValue
} else {
self.reference = Ref(value: newValue)
}
}
}
}
var box1 = Box(interior: 0)
var box2 = box1
//same addresses
print(address(&box1)) //0x7ffee11b53d0
print(address(&box2)) //0x7ffee11b53c0
//COW is used
box2.value = 1
print(address(&box2)) //0x7ffee11b53c0
print(box1.value) //0 // <-
print(box2.value) //1