我们知道,结构的一个问题是它们是按值传递的,因此在将结构传递给函数或从函数返回时会产生一个副本.如果你有一个大的结构(比如说有12个属性),那么这种复制可能会变得昂贵.
这通常由人们说,swift编译器和/或LLVM可以删除副本(即传递对结构的引用,而不是复制它),并且只有在实际改变结构时才需要复制.
这一切都很好,但它总是在理论上被谈到 – “作为优化,LLVM可以忽略副本”和类似的东西.
我的问题是,谁能告诉我们究竟发生了什么?编译器是否真的忽略了副本,或者它只是一天可能存在的理论上的未来优化? (例如,C#编译器理论上也可以删除struct副本,但它实际上从未实现过,并且Microsoft建议您不要对大于16字节的事物使用结构[1])
如果swift确实存在elide结构副本,那么是否以及何时执行此操作会有一些解释或启发式吗?
注意:我在谈论用户定义的结构,而不是像数组和字典这样的stdlib内置
[1] https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct
首先,Swift不使用平台的调用约定.在macOS,C,C和Objective-C都使用x86_64 System V ABI,但Swift没有.一个显着的变化是Swift的CC有四个返回GPR(rax,rdx,rcx,r8)而不是两个.当你混合使用浮点数时,几乎肯定会变得更复杂,但是如果你去所有整数和类似整数的类型(比如指针),结果会通过寄存器传递并返回,如果它们适合宽度为at最多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上的-O,withLet做了以下权衡:
> l.large被复制到本地临时
> l在复制之后和doSomething调用之前被释放
使用可变或计算属性的副本是不可避免的(因为它们的值可以在调用的持续时间内发生变化),但我想,在可能性的范围内,让常量可以直接通过地址传递.但是,在那种情况下,我必须保持活着直到doSomething返回之后.