Array
is 实施的具有写时复制行为 - 无论任何编译器优化如何,您都会得到它(当然,优化可以减少需要发生复制的情况数量)。
在基本层面上,Array
只是一个结构体,它保存对包含元素的堆分配缓冲区的引用 - 因此多个Array
实例可以参考same缓冲。当您要改变给定的数组实例时,实现将检查缓冲区是否被唯一引用,如果是,则直接改变它。否则,数组将执行底层缓冲区的副本以保留值语义。
然而,随着你的Point
结构——您没有在语言级别实现写时复制。当然,作为@亚历山大 说 https://stackoverflow.com/questions/43486408/does-swift-copy-on-write-for-all-structs#comment74028610_43486408,这不会阻止编译器执行各种优化以最小化复制整个结构的成本。不过,这些优化不必遵循写时复制的确切行为——编译器可以自由地执行whatever它希望,只要程序按照语言规范运行即可。
在你的具体例子中,两者p1
and p2
是全局的,因此编译器需要使它们成为不同的实例,因为同一模块中的其他 .swift 文件可以访问它们(尽管这可能会通过整个模块优化来优化)。然而,编译器仍然不需要复制实例——它只需在编译时评估浮点加法 https://gist.github.com/hamishknight/edbcc3b9cc92158a35488ce28108fe9f#constant-evalutation-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
,然后打印出来。
关于将值类型作为函数参数传递,对于足够大的类型和(在结构的情况下)利用足够属性的函数,编译器可以通过他们引用 https://gist.github.com/hamishknight/edbcc3b9cc92158a35488ce28108fe9f#optimising-value-type-parameters-to-pass-by-reference而不是复制。然后,被调用者仅在需要时才可以制作它们的副本,例如需要使用可变副本时。
在结构按值传递的其他情况下,编译器也可以专门化功能 https://gist.github.com/hamishknight/edbcc3b9cc92158a35488ce28108fe9f#specialising-functions-to-only-take-the-property-values-of-a-structure-that-they-need为了仅复制函数所需的属性。
对于以下代码:
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
's x
属性到函数中,从而节省了复制的成本y
财产。
因此编译器会尽其所能来减少值类型的复制。但是,在不同情况下有如此多的优化,您不能简单地将任意值类型的优化行为归结为写入时复制。