首页 > 解决方案 > swift 实际上对结构做了什么复制省略?

问题描述

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

众所周知,结构的一个问题是它们是按值传递的,因此当您将结构传递给函数或从函数返回时,会产生副本。如果您有一个大型结构(例如其中包含 12 个属性),那么这种复制可能会变得昂贵。

这通常被人们辩护说 swift 编译器和/或 LLVM 可以删除副本(即传递对结构的引用,而不是复制它)并且只需要在您实际改变结构时进行复制。

这一切都很好,但总是用理论术语来谈论——“作为一种优化,LLVM可以省略副本”之类的东西。

我的问题是,谁能告诉我们实际发生了什么?编译器实际上是否删除了副本,或者它只是将来可能存在的理论上的优化?(例如,C# 编译器理论上也可以省略结构副本,但它实际上从未这样做过,Microsoft 建议您不要将结构用于大于 16 字节的内容 [1])

如果 swift 确实省略了 struct 副本,是否有一些解释或启发式来说明它是否以及何时这样做?

注意:我说的是用户定义的结构,而不是像数组和字典这样的 stdlib 内置的东西

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

标签: swiftswift-structs

解决方案


首先,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)
}

在此示例中,在-OSwift 4.1 上,withLet进行了以下权衡:

  • l.large被复制到本地临时
  • l在复制之后和调用之前 释放doSomething

具有可变或计算属性的副本将是不可避免的(因为它们的值可以在调用期间发生变化),但我认为let常量可以直接通过地址传递是有可能的。但是,在那种情况下,l将不得不活着直到doSomething回来之后。


推荐阅读