如果我使用类似...的功能创建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是否在编译时优化了所有这些未使用的值?
答案 0 :(得分:6)
不,这不是最佳化。它将为每个呼叫创建一个新副本。很难想象优化器会如何避免复制,但是编写优化器的向导之前曾骗过我。但是,与大多数优化程序问题一样,我们不必猜测它的作用。我们可以看看。
我稍微重写了一下这段代码,以摆脱可选项(这些可选项只是使事情稍微复杂了一点,而没有改变问题)。
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”和“昨天”减少为它们的标记字符串值,并将整个块减少为单个init
调用(在%22),因为它注意到我正在设置所有值。太神奇了。