所有结构的写时快速复制吗?

问题描述 投票:0回答:2

我知道 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
swift value-type copy-on-write
2个回答
52
投票

Array
implemented with copy-on-write behavior – 你会得到它,不管任何编译器优化(当然,优化可以减少需要发生复制的情况的数量)。

在基本层面上,

Array
只是一个结构,它保存对包含元素的堆分配缓冲区的引用——因此多个
Array
实例可以引用 same 缓冲区。当您要改变给定的数组实例时,实现将检查缓冲区是否被唯一引用,如果是,则直接改变它。否则,数组将执行底层缓冲区的副本以保留值语义。

但是,使用您的

Point
结构 – 您没有在语言级别实现写时复制。当然,正如 @Alexander 所说,这并不能阻止编译器执行各种优化以最小化复制整个结构的成本。这些优化不需要遵循写时复制的确切行为——只要程序按照语言规范运行,编译器就可以自由地做它想做的whatever

在您的特定示例中,

p1
p2
都是全局的,因此编译器需要使它们成为不同的实例,因为同一模块中的其他 .swift 文件可以访问它们(尽管这可能会被整个优化掉-模块优化)。然而,编译器仍然不需要复制实例——它可以只在编译时评估浮点加法并用
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
的单个浮点局部变量,然后打印它。

关于将值类型作为函数参数传递,对于足够大的类型和(在结构的情况下)充分利用其属性的函数,编译器可以通过引用传递它们而不是复制。然后,被调用者可以仅在需要时制作它们的副本,例如需要使用可变副本时。

在结构按值传递的其他情况下,编译器也可以

专门化函数以便只复制函数需要的属性。

对于以下代码:

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
属性的成本。

所以编译器会尽其所能减少值类型的复制。但是在不同情况下有这么多不同的优化,你不能简单地将任意值类型的优化行为归结为写时复制。


5
投票

写时快速复制(COW)

只有在必要时才复印一份(例如,当我们更改/写入时)。 默认

Value Type

【关于】不支持COW机制。但是像Collections
(Array, Dictionary, Set)这样的一些系统结构支持它

打印地址

// 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 for a2 is not used a2.value = 1 print(address(&a2)) //0x7ffee48f24a0
带有 COW 的值类型(集合)

//collection(COW is realized) var collection1 = [A()] var collection2 = collection1 //same addresses print(address(&collection1)) //0x600000c2c0e0 print(address(&collection2)) //0x600000c2c0e0 //COW for collection2 is used collection2.append(A()) print(address(&collection2)) //0x600000c2c440

对大值使用 COW 语义 以尽量减少每次复制数据。有两种常见的方式:

    使用支持 COW 的值类型的包装器。
  1. 使用具有
  2. reference 的包装器来堆放我们可以保存大数据的地方。重点是:
    我们能够创建 lite wrapper 的多个副本,这些副本将指向堆中的相同大数据
  • 当我们尝试修改(写入)一个带有大数据副本的新引用时,将创建 - COW 正在运行。
  • AnyObject.isKnownUniquelyReferenced()
     可以说是否有
    单一引用
    这个对象
struct Box<T> { fileprivate var ref: Ref<T> init(value: T) { self.ref = Ref(value: value) } var value: T { get { return ref.value } set { //it is true when there is only one(single) reference to this object //that is why it is safe to update, //if not - new reference to heap is created with a copy of value if (isKnownUniquelyReferenced(&self.ref)) { self.ref.value = newValue } else { self.ref = Ref(value: newValue) } } } final class Ref<T> { var value: T init(value: T) { self.value = value } } }
let value = 0

var box1 = Box(value: value)
var box2 = box1

//same addresses
print(address(&box1.ref.value)) //0x600000ac2490
print(address(&box2.ref.value)) //0x600000ac2490

box2.value = 1

print(box1.value) //0
print(box2.value) //1

//COW in action
//different addresses
print(address(&box1.ref.value)) //0x600000ac2490
print(address(&box2.ref.value)) //0x600000a9dd30
    
© www.soinside.com 2019 - 2024. All rights reserved.