swift实际上为结构做了什么样的复制?

时间:2018-05-16 02:47:23

标签: swift swift-structs

Swift编程的普遍共识(截至2018年5月,Swift 4.1,Xcode 9.3)是结构应该是首选,除非你的逻辑明确要求对象的共享引用。

正如我们所知,结构的一个问题是它们是按值传递的,因此在将结构传递给函数或从函数返回时会产生一个副本。如果你有一个大型结构(比如说有12个属性),那么这种复制可能会很昂贵。

这通常是由人们说,swift编译器和/或LLVM可以忽略副本(即传递对结构的引用,而不是复制它),并且只有在实际改变结构时才需要复制。

这一切都很好,但它总是在理论上被谈论 - "作为优化,LLVM 可以忽略副本"和那样的东西。

我的问题是,有人能告诉我们实际会发生什么? 编译器是否实际上忽略了副本,或者它只是一天可能存在的理论上的未来优化? (例如,C#编译器理论上也可以删除struct副本,但它实际上从未实现过,并且Microsoft建议您不要将结构用于大于16字节的内容[1]) < / p>

如果swift确实存在elide结构副本,那么是否以及何时执行此操作会有一些解释或启发式吗?

注意:我正在讨论用户定义的结构,而不是像数组和字典这样的stdlib内置

[1] https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct

1 个答案:

答案 0 :(得分:5)

首先,Swift不使用平台的调用约定。在macOS,C,C ++和Objective-C都使用x86_64 System V ABI,但Swift没有。一个值得注意的变化是Swift的CC有四个返回GPR(rax,rdx,rcx,r8)而不是两个。

当你混合使用浮点数时,几乎肯定会变得更复杂,但是如果你去所有整数和类似整数的类型(比如指针),结构将通过寄存器传递并返回,如果它们适合于宽度最多4个寄存器。在此之上,结构通过地址传递和返回。在返回值的情况下,调用者负责设置堆栈空间并将该空间的地址作为隐藏参数传递给被调用者。

由于Swift ABI尚未最终确定,可能仍有可能发生变化。

然而,仅仅传递指针并不代表没有复制发生。例如:

public class Let {
    let large: Large

    init(large: Large) {
        self.large = large
    }
}

public func withLet(l: Let) {
    doSomething(foo: l.large)
}

在此示例中,在Swift 4.1上的-OwithLet进行以下权衡:

  • l.large被复制到本地临时
  • 复制后发布
  • ldoSomething之前

使用可变或计算属性的副本是不可避免的(因为它们的值可能会在调用期间发生变化),但我认为let常量可能存在于l常量的范围内直接通过地址传递。但是,在这种情况下,doSomething必须保持活着,直到@Override public int getItemCount() { return productList.size(); } 返回。