是否快速复制所有结构的写入?

glo*_*loo 23 value-type copy-on-write swift

我知道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
Run Code Online (Sandbox Code Playgroud)

Ham*_*ish 31

Array实现与写入时复制行为-你会得到它无论任何编译器的优化的(尽管当然,最佳化可以减少其中一个副本需要发生的病例数).

在基本级别,Array它只是一个结构,它包含对包含元素的堆分配缓冲区的Array引用- 因此多个实例可以引用相同的缓冲区.当你来改变给定的数组实例时,实现将检查缓冲区是否被唯一引用,如果是,则直接改变它.否则,该数组将执行底层缓冲区的副本,以保留值语义.

但是,使用您的Point结构 - 您不是在语言级别实现写时复制.当然,正如@Alexander所说,这并不能阻止编译器执行各种优化,以最大限度地降低复制整个结构的成本.这些优化不需要遵循copy-on-write的确切行为 - 只要程序根据语言规范运行,编译器就可以随心所欲地做任何事情.

在您的具体的例子,无论是p1p2是全球性的,因此编译器需要,使他们不同的情况下,作为同一模块中的其他文件.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()
Run Code Online (Sandbox Code Playgroud)

编译器甚至不必创建两个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)
Run Code Online (Sandbox Code Playgroud)

假设foo(p:)没有编译器内联(它将在本例中,但是一旦其实现达到一定大小,编译器就不会认为值得) - 编译器可以将函数专门化为:

func foo(px: Float) {
    print(px)
}

foo(px: 0)
Run Code Online (Sandbox Code Playgroud)

它只将Point's x属性的值传递给函数,从而节省了复制y属性的成本.

因此,编译器将尽其所能,以减少值类型的复制.但是,在不同情况下进行了如此多的各种优化,您不能简单地将任意值类型的优化行为简化为只写入时复制.

  • 因为在语言级实现copy-on-write需要引用,因此需要堆分配和引用计数的成本.现在试图改变你的逻辑而不确定你是在做出更好还是更坏的事情会适得其反. (4认同)
  • @gloo如果当前还不是性能瓶颈,我真的不会担心。如前所述,编译器能够执行许多优化以减少值类型的复制数量。如果后来出现问题,您可以相对容易地重构您的结构以使用写时复制;但是只有在进行概要分析时将其识别为问题之后,并且在看到进行更改实际上会提高性能之后,才应该这样做。 (3认同)
  • 好吧,我用swift / metal编写了一个游戏引擎。我绕过了很多结构,这些结构代表GPU和当前帧数据要消耗的绘制命令。当时我以为我的所有结构都将使用COW来避免浪费副本,但是后来我了解到实际上在Xcode的实际操作上存在很多分歧。所以我变得担心我的引擎没有像我想的那样优化。我的游戏以60fps的速度运行,所以现在这不是问题,只是担心它不能很好地适应未来的项目。 (2认同)

yoA*_*ex5 6

快速写入时复制 (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
}
Run Code Online (Sandbox Code Playgroud)

值类型默认行为

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

对大值使用 COW 语义,以最大限度地减少每次复制数据。常见的方式有两种:

  1. 使用支持 COW 的值类型的包装器。
  2. 使用一个包装器,它引用,我们可以在其中保存大量数据。重点是:
  • 我们能够创建 lite 包装器的多个副本,这些副本将指向堆中相同的大数据
  • 当我们尝试修改(写入)时,将创建一个包含大数据副本的新引用 - 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)