代码之家  ›  专栏  ›  技术社区  ›  Chris Allinson

如何在ivar数组上使用inout?[副本]

  •  0
  • Chris Allinson  · 技术社区  · 6 年前

    我知道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
    
    0 回复  |  直到 8 年前
        1
  •  28
  •   Community CDub    8 年前

    Array 实施 使用copy-on-write行为,无论编译器如何优化,您都可以获得它(当然,优化可以减少需要进行复制的情况的数量)。

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

    但是,与您的 Point 结构:您没有在语言级别实现写时复制。当然,因为 @Alexander says ,这并不能阻止编译器执行各种优化以最小化复制整个结构的成本。这些优化不需要遵循copy-on-write的确切行为,尽管编译器可以自由地执行。 无论什么 它希望,只要程序按照语言规范运行。

    在您的具体示例中,两者都是 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()
    

    编译器甚至不需要创建两个 实例以_“开头,它只能创建一个初始化为 然后打印出来。

    关于将值类型作为函数参数传递,对于足够大的类型和(对于结构)使用足够多属性的函数,编译器 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)
    

    它只传递 x 属性转换为函数,从而节省了复制 y 属性。

    因此编译器将尽其所能减少值类型的复制。但是,由于在不同的环境中有如此多的各种优化,您不能简单地将任意值类型的优化行为归结为只进行写时复制。