Swift 中的变异结构函数是否会创建 self 的新副本?

问题描述 投票:0回答:4

我喜欢 swift 中的值语义,但我担心变异函数的性能。假设我们有以下

struct

struct Point {
   var x = 0.0
   mutating func add(_ t:Double){
      x += t
   }
}

现在假设我们创建一个

Point
并对其进行变异:

var p = Point()
p.add(1)

现在内存中现有的

struct
是否发生变异,或者
self
被替换为新实例,如

所示
self = Point(x:self.x+1)
swift performance struct swift-structs
4个回答
19
投票

现在内存中的现有结构是否发生变异,或者被新实例自行替换

从概念上讲,这两个选项完全相同。我将使用这个示例结构,它使用 UInt8 而不是 Double(因为它的位更容易可视化)。

struct Point {
    var x: UInt8
    var y: UInt8

    mutating func add(x: UInt8){
       self.x += x
    }
}

假设我创建了这个结构的一个新实例:

var p = Point(x: 1, y: 2)

这会在堆栈上静态分配一些内存。它看起来像这样:

00000000  00000001  00000010  00000000
<------^  ^------^  ^------^ ^----->
other     |self.x | self.y | other memory
          ^----------------^
          the p struct

让我们看看当我们调用

p.add(x: 3)
时这两种情况会发生什么:

  1. 现有结构已就地突变:

    我们在内存中的结构将如下所示:

     00000000  00000100  00000010  00000000
     <------^  ^------^  ^------^ ^----->
     other    | self.x | self.y | other memory
             ^----------------^
             the p struct
    
  2. Self 被新实例替换:

    我们在内存中的结构将如下所示:

     00000000  00000100  00000010  00000000
     <------^  ^------^  ^------^ ^----->
     other    | self.x | self.y | other memory
             ^----------------^
             the p struct
    

请注意,这两种情况没有区别。这是因为为 self 分配新值会导致就地突变。

p
始终是堆栈上相同的两个内存字节。为 self 分配一个新值到
p
只会替换这 2 个字节的内容,但它仍然是相同的两个字节。

现在,这两种情况之间存在一个差异,即处理初始化程序任何可能的副作用。假设这是我们的结构: struct Point { var x: UInt8 var y: UInt8 init(x: UInt8, y: UInt8) { self.x = x self.y = y print("Init was run!") } mutating func add(x: UInt8){ self.x += x } }

当您运行 
var p = Point(x: 1, y: 2)

时,您会看到打印了

Init was run!
(如预期)。但是当您运行
p.add(x: 3)
时,您会发现没有再打印任何内容。这告诉我们初始化器不是新的。
    


15
投票

struct Point { var x = 0.0 mutating func add(_ t: Double){ x += t } } var p = Point() p.add(1)

我们可以看到 
add(_:)

方法发出为:


// Point.add(Double) -> () sil hidden @main.Point.add (Swift.Double) -> () : $@convention(method) (Double, @inout Point) -> () { // %0 // users: %7, %2 // %1 // users: %4, %3 bb0(%0 : $Double, %1 : $*Point): // get address of the property 'x' within the point instance. %4 = struct_element_addr %1 : $*Point, #Point.x, loc "main.swift":14:9, scope 5 // user: %5 // get address of the internal property '_value' within the Double instance. %5 = struct_element_addr %4 : $*Double, #Double._value, loc "main.swift":14:11, scope 5 // users: %9, %6 // load the _value from the property address. %6 = load %5 : $*Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // user: %8 // get the _value from the double passed into the method. %7 = struct_extract %0 : $Double, #Double._value, loc "main.swift":14:11, scope 5 // user: %8 // apply a builtin floating point addition operation (this will be replaced by an 'fadd' instruction in IR gen). %8 = builtin "fadd_FPIEEE64"(%6 : $Builtin.FPIEEE64, %7 : $Builtin.FPIEEE64) : $Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // user: %9 // store the result to the address of the _value property of 'x'. store %8 to %5 : $*Builtin.FPIEEE64, loc "main.swift":14:11, scope 5 // id: %9 %10 = tuple (), loc "main.swift":14:11, scope 5 %11 = tuple (), loc "main.swift":15:5, scope 5 // user: %12 return %11 : $(), loc "main.swift":15:5, scope 5 // id: %12 } // end sil function 'main.Point.add (Swift.Double) -> ()'

(通过运行

xcrun swiftc -emit-sil main.swift | xcrun swift-demangle > main.silgen



这里重要的是 Swift 如何处理隐式 

self

参数。您可以看到它已作为

@inout
 参数发出,这意味着它将通过 
reference
传递到函数中。 为了执行

x

属性的突变,使用

struct_element_addr
 SIL 指令来查找其地址,然后查找 
_value
的底层
Double
属性。然后,使用
store
 指令将所得的双精度值简单地存储回该地址。
这意味着

add(_:)

方法能够直接更改内存中

p
x
属性的值,而无需创建
Point
的任何中间实例。
    


1
投票

import Foundation struct Point { var x = 0.0 mutating func add(_ t:Double){ x += t } } var p = Point() withUnsafePointer(to: &p) { print("\(p) has address: \($0)") } p.add(1) withUnsafePointer(to: &p) { print("\(p) has address: \($0)") }

并在输出中获得:

点(x:0.0)的地址:0x000000010fc2fb80

点(x:1.0)的地址:0x000000010fc2fb80

考虑到内存地址没有改变,我敢打赌结构已经被改变,而不是被替换。

要完全替换某些东西,你必须使用另一个内存地址,因此将对象复制回原始内存地址中是没有意义的。


0
投票

© www.soinside.com 2019 - 2024. All rights reserved.