快速写入时复制是否适用于所有结构?

pn9klfpd  于 2022-10-23  发布在  Swift
关注(0)|答案(2)|浏览(203)

我知道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
xjreopfe

xjreopfe1#

Array是用写时复制行为实现的--无论进行任何编译器优化,您都会得到它(当然,优化可以减少需要进行复制的情况)。
在基本级别上,Array只是一个结构,它包含对包含元素的堆分配缓冲区的引用-因此,多个Array示例可以引用
相同的*缓冲区。当您对给定的数组示例进行变异时,实现将检查缓冲区是否被唯一引用,如果是,则直接对其进行变异。否则,数组将执行底层缓冲区的副本,以保留值语义。
然而,对于您的Point结构,您并没有在语言级别实现写入时复制。当然,正如@Alexander所说,这并不能阻止编译器执行各种优化以最小化复制整个结构的成本。这些优化不需要遵循写时复制的确切行为-编译器只需自由地做它想做的任何事情,只要程序按照语言规范运行即可。
在您的特定示例中,p1p2都是全局的,因此编译器需要使它们成为不同的示例,因为同一模块中的其他.swft文件可以访问它们(尽管这可能会通过全模块优化来优化)。但是,编译器仍然不需要复制示例-它只需在编译时计算浮点加法,并使用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)

它只将Pointx属性的值传递给该函数,从而节省了复制y属性的成本。
因此,编译器将尽其所能减少值类型的复制。但是,由于在不同的环境中有这么多不同的优化,您不能简单地将任意值类型的优化行为归结为只在写入时复制。

iecba09b

iecba09b2#

写入时快速拷贝(COW)

默认情况下,Value TypeAbout(https://stackoverflow.com/a/59219141/4770877)不支持COW(写入时拷贝)机制。但是像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(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引用堆,则该引用的值在不同的value types之间共享

//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

解决方案是使用某种自写的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

相关问题