glo*_*loo 5 performance struct swift swift-structs
我喜欢swift中的值语义,但我担心变异函数的性能.假设我们有以下内容struct
struct Point {
var x = 0.0
mutating func add(_ t:Double){
x += t
}
}
Run Code Online (Sandbox Code Playgroud)
现在假设我们创建一个Point并将其变异为:
var p = Point()
p.add(1)
Run Code Online (Sandbox Code Playgroud)
现在内存中的现有结构变异,或者被struct替换为新的实例
self = Point(x:self.x+1)
Run Code Online (Sandbox Code Playgroud)
Ham*_*ish 11
我觉得值得一看(从相当高的层次)编译器在这里做了什么。如果我们看一下发出的规范 SIL:
struct Point {
var x = 0.0
mutating func add(_ t: Double){
x += t
}
}
var p = Point()
p.add(1)
Run Code Online (Sandbox Code Playgroud)
我们可以看到该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) -> ()'Run Code Online (Sandbox Code Playgroud)
(通过运行xcrun swiftc -emit-sil main.swift | xcrun swift-demangle > main.silgen)
这里重要的是 Swift 如何处理隐式self参数。您可以看到它已作为@inout参数发出,这意味着它将通过引用传递到函数中。
为了执行x属性的struct_element_addr变更,使用SIL 指令来查找其地址,然后_value是Double. 然后将结果双精度值与store指令一起简单地存储回该地址。
这意味着该add(_:)方法能够直接更改内存中p的x属性值,而无需创建 的任何中间实例Point。
现在,内存中的现有结构变异,或者自我替换为新实例
从概念上讲,这两个选项完全相同.我将使用这个示例结构,它使用UInt8而不是Double(因为它的位更容易可视化).
struct Point {
var x: UInt8
var y: UInt8
mutating func add(x: UInt8){
self.x += x
}
}
Run Code Online (Sandbox Code Playgroud)
并假设我创建了这个结构的新实例:
var p = Point(x: 1, y: 2)
Run Code Online (Sandbox Code Playgroud)
这静态地在堆栈上分配一些内存.它看起来像这样:
00000000 00000001 00000010 00000000
<------^ ^------^ ^------^ ^----->
other | self.x | self.y | other memory
^----------------^
the p struct
Run Code Online (Sandbox Code Playgroud)
让我们看看当我们打电话时两种情况会发生什么p.add(x: 3):
现有结构就地变异:
我们在内存中的结构将如下所示:
00000000 00000100 00000010 00000000
<------^ ^------^ ^------^ ^----->
other | self.x | self.y | other memory
^----------------^
the p struct
Run Code Online (Sandbox Code Playgroud)Self被替换为新实例:
我们在内存中的结构将如下所示:
00000000 00000100 00000010 00000000
<------^ ^------^ ^------^ ^----->
other | self.x | self.y | other memory
^----------------^
the p struct
Run Code Online (Sandbox Code Playgroud)请注意,这两种方案之间没有区别.那是因为为自己分配一个新值导致就地变异.p堆栈上的内存总是相同的两个字节.为self分配一个新值p只会替换这两个字节的内容,但它仍然是相同的两个字节.
现在两种情况之间可能存在一个差异,并且处理初始化程序的任何可能的副作用.假设这是我们的结构,相反:
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
}
}
Run Code Online (Sandbox Code Playgroud)
当你跑步时var p = Point(x: 1, y: 2),你会看到它Init was run!被打印出来(如预期的那样).但是当你跑步时p.add(x: 3),你会看到没有任何进一步印刷.这告诉我们初始化器不是新的.
| 归档时间: |
|
| 查看次数: |
1098 次 |
| 最近记录: |