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一次又一次地创建一个新的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”和“昨天”减小为其标记字符串值,并将整个块减少为一个init调用(%22),因为它注意到我正在设置所有值。太棒了。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章