【问题标题】:What copy-elision does swift actually do for structs?swift 实际上对结构做了什么复制省略?
【发布时间】:2018-05-16 02:47:22
【问题描述】:

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

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

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

这一切都很好,但总是在理论上谈论它 - “作为一种优化,LLVM可以删除副本”之类的东西。

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

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

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

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

【问题讨论】:

  • 当您在这里指的是 C++ 术语时,我将指出 C++ 副本有可能比 Swift 副本大几个数量级。在 C++ 中复制一个向量 10,000 个重要对象的成本将使复制一个具有 12 个属性的“大型结构”的成本相形见绌。
  • 编译器还可以专门化接受值类型参数的函数,以便只传递函数体中使用的属性,比较stackoverflow.com/q/43486408/2976878

标签: swift swift-structs


【解决方案1】:

首先,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 被复制到本地临时
  • l 在复制之后释放,之前 doSomething 被调用

对于可变或计算属性,副本将是不可避免的(因为它们的值可以在调用期间发生变化),但我认为let 常量可以直接通过地址传递是有可能的。但是,在这种情况下,l 必须在doSomething 返回之前保持活动状态。

【讨论】:

  • 您的回答暗示,对于大于 4 个寄存器的不可变结构,它们在传递给函数时被复制,但没有明确说明这一点 - 我读对了吗?
  • @OrionEdwards,我没有这么说,因为它被答案的后半部分严重调制了。在实践中,尽管有证据表明它试图最小化它们,但很难预测编译器是否会复制。在this example 中,使用Swift 4.1,large 被构造一次并通过地址直接传递给foo,它不会复制它。但是,在this other similar example 中,编译器创建large 副本,只是为了更改第一个字段,然后将其传递给第二个foo 调用。
  • (我应该澄清第二个副本是从常量 (5, 6, 5, 4, 3, 2, 1, 0) 元组创建的,而不是从其他结构创建的。这让我相信这是 LLVM 使用的 SSA 形式的结果,但是它仍然可以作为我的副本。)
猜你喜欢
  • 2014-07-14
  • 2013-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
  • 2020-11-21
  • 2015-02-26
相关资源
最近更新 更多