Swift是否优化结构的链式创建和复制



如果我创建了一个具有以下函数的Struct。。。

struct SomeStruct {
var name: String? = nil
var number: Int = 0
var date: Date? = nil
//... many other properties
func setting<Value>(_ keyPath: WritableKeyPath<SomeStruct, Value>, to value: Value) -> SomeStruct {
var copy = self
copy[keyPath: keyPath] = value
return copy
}
}

Swift在做类似…的事情时做了任何优化吗。。。

let myStruct = SomeStruct()
.setting(.name, to: "Fogmeister")
.setting(.number, to: 42)
.setting(.date, to: yesterday)
.setting(.otherProperty, to: value)
...etc
...etc

因为setting函数会创建一个副本,并在每次基本上一遍又一遍地创建新的Struct,然后丢弃除一个之外的所有副本时更改副本。

在执行此操作时,是否需要考虑任何开销因素,或者Swift是否在编译时优化掉了所有这些未使用的值?

不,这不是优化的。它会为每个电话制作一个新的副本。很难想象优化器会如何避免复制,但编写优化器的向导以前曾欺骗过我。但与大多数优化器问题一样,我们不必猜测它的作用。我们可以看看。

我稍微重写了一下这段代码,去掉了选项(这只是在不改变问题的情况下使事情稍微复杂一些(。

struct SomeStruct {
var name: String = ""
var number: Int = 0
var date: String = ""
func setting<Value>(_ keyPath: WritableKeyPath<SomeStruct, Value>, to value: Value) -> SomeStruct {
var copy = self
copy[keyPath: keyPath] = value
return copy
}
}
let myStruct = SomeStruct()
.setting(.name, to: "Fogmeister")
.setting(.number, to: 42)
.setting(.date, to: "yesterday")

然后通过优化将其编译为SIL:

swiftc -O -emit-sil x.swift

setting方法变为:

// SomeStruct.setting<A>(_:to:)
sil hidden @$S1x10SomeStructV7setting_2toACs15WritableKeyPathCyACxG_xtlF : $@convention(method) <Value> (@guaranteed WritableKeyPath<SomeStruct, Value>, @in_guaranteed Value, @guaranteed SomeStruct) -> @owned SomeStruct {
// %0                                             // users: %26, %17, %18, %3
// %1                                             // users: %11, %4
// %2                                             // users: %8, %7, %9, %5
bb0(%0 : $WritableKeyPath<SomeStruct, Value>, %1 : $*Value, %2 : $SomeStruct):
debug_value %0 : $WritableKeyPath<SomeStruct, Value>, let, name "keyPath", argno 1 // id: %3
debug_value_addr %1 : $*Value, let, name "value", argno 2 // id: %4
debug_value %2 : $SomeStruct, let, name "self", argno 3 // id: %5
%6 = alloc_stack $SomeStruct, var, name "copy"  // users: %12, %28, %9, %29
%7 = struct_extract %2 : $SomeStruct, #SomeStruct.name // user: %15
%8 = struct_extract %2 : $SomeStruct, #SomeStruct.date // user: %16
store %2 to %6 : $*SomeStruct                   // id: %9
%10 = alloc_stack $Value                        // users: %27, %24, %11
copy_addr %1 to [initialization] %10 : $*Value  // id: %11
%12 = address_to_pointer %6 : $*SomeStruct to $Builtin.RawPointer // user: %13
%13 = struct $UnsafeMutablePointer<SomeStruct> (%12 : $Builtin.RawPointer) // user: %18
// function_ref _projectKeyPathWritable<A, B>(root:keyPath:)
%14 = function_ref @$Ss23_projectKeyPathWritable4root03keyC0Spyq_G_yXlSgtSpyxG_s0dbC0Cyxq_Gtr0_lF : $@convention(thin) <τ_0_0, τ_0_1> (UnsafeMutablePointer<τ_0_0>, @guaranteed WritableKeyPath<τ_0_0, τ_0_1>) -> (UnsafeMutablePointer<τ_0_1>, @owned Optional<AnyObject>) // user: %18
retain_value %7 : $String                       // id: %15
retain_value %8 : $String                       // id: %16
strong_retain %0 : $WritableKeyPath<SomeStruct, Value> // id: %17
%18 = apply %14<SomeStruct, Value>(%13, %0) : $@convention(thin) <τ_0_0, τ_0_1> (UnsafeMutablePointer<τ_0_0>, @guaranteed WritableKeyPath<τ_0_0, τ_0_1>) -> (UnsafeMutablePointer<τ_0_1>, @owned Optional<AnyObject>) // users: %25, %19, %20
%19 = tuple_extract %18 : $(UnsafeMutablePointer<Value>, Optional<AnyObject>), 0 // user: %21
%20 = tuple_extract %18 : $(UnsafeMutablePointer<Value>, Optional<AnyObject>), 1 // user: %23
%21 = struct_extract %19 : $UnsafeMutablePointer<Value>, #UnsafeMutablePointer._rawValue // user: %22
%22 = pointer_to_address %21 : $Builtin.RawPointer to [strict] $*Value // user: %23
%23 = mark_dependence %22 : $*Value on %20 : $Optional<AnyObject> // user: %24
copy_addr [take] %10 to %23 : $*Value           // id: %24
release_value %18 : $(UnsafeMutablePointer<Value>, Optional<AnyObject>) // id: %25
strong_release %0 : $WritableKeyPath<SomeStruct, Value> // id: %26
dealloc_stack %10 : $*Value                     // id: %27
%28 = load %6 : $*SomeStruct                    // user: %30
dealloc_stack %6 : $*SomeStruct                 // id: %29
return %28 : $SomeStruct                        // id: %30
} // end sil function '$S1x10SomeStructV7setting_2toACs15WritableKeyPathCyACxG_xtlF'

特别感兴趣的是这一部分:

%6 = alloc_stack $SomeStruct, var, name "copy"  // users: %12, %28, %9, %29
%7 = struct_extract %2 : $SomeStruct, #SomeStruct.name // user: %15
%8 = struct_extract %2 : $SomeStruct, #SomeStruct.date // user: %16
store %2 to %6 : $*SomeStruct                   // id: %9

正如预期的那样,每次调用setting时都会创建一个新的副本。

IMO,Swift中更好的方法是:

let myStruct: SomeStruct = { 
var s = SomeStruct()
s.name = "Fogmeister"
s.number = 42
s.date = "yesterday"
return s
}()

这优化到以下内容(加上我的注释(:

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
# allocate some storage for myStruct as a global
alloc_global @$S1x8myStructAA04SomeB0Vvp        // id: %2
%3 = global_addr @$S1x8myStructAA04SomeB0Vvp : $*SomeStruct // user: %23
# Construct the tagged string value for "Fogmeister"
%4 = integer_literal $Builtin.Int64, 8391166415069474630 // user: %9
%5 = integer_literal $Builtin.Int64, -1585267068834385307 // user: %6
%6 = struct $UInt (%5 : $Builtin.Int64)         // user: %7
%7 = value_to_bridge_object %6 : $UInt          // user: %8
%8 = struct $_StringObject (%7 : $Builtin.BridgeObject) // user: %10
%9 = struct $UInt (%4 : $Builtin.Int64)         // user: %10
%10 = struct $_StringGuts (%8 : $_StringObject, %9 : $UInt) // user: %11
%11 = struct $String (%10 : $_StringGuts)       // user: %22
# Construct the 42
%12 = integer_literal $Builtin.Int64, 42        // user: %13
%13 = struct $Int (%12 : $Builtin.Int64)        // user: %22
# Construct the tagged string for "yesterday"
%14 = integer_literal $Builtin.Int64, -1657324662872342407 // user: %15
%15 = struct $UInt (%14 : $Builtin.Int64)       // user: %16
%16 = value_to_bridge_object %15 : $UInt        // user: %18
%17 = integer_literal $Builtin.Int64, 7017859899421058425 // user: %19
%18 = struct $_StringObject (%16 : $Builtin.BridgeObject) // user: %20
%19 = struct $UInt (%17 : $Builtin.Int64)       // user: %20
%20 = struct $_StringGuts (%18 : $_StringObject, %19 : $UInt) // user: %21
%21 = struct $String (%20 : $_StringGuts)       // user: %22
# init SomeStruct and store it in our global
%22 = struct $SomeStruct (%11 : $String, %13 : $Int, %21 : $String) // user: %23
store %22 to %3 : $*SomeStruct                  // id: %23
# Return 0 (cause it's main)
%24 = integer_literal $Builtin.Int32, 0         // user: %25
%25 = struct $Int32 (%24 : $Builtin.Int32)      // user: %26
return %25 : $Int32                             // id: %26
} // end sil function 'main'

您会注意到,闭包执行已经完全优化。编译器能够将"Fogmeister"one_answers"yestery"缩减为其标记的字符串值,并将整个块缩减为单个init调用(位于%22(,因为它注意到我正在设置所有值。太神奇了。

最新更新