Does Swift optimise chained creation and copy of structs?










1















If I create a Struct with a function like...



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




Does Swift do any optimisation on doing something like...



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


Because the setting function creates a copy and changes the copy each time you are essentially creating a new Struct over and over and over and then throwing away all but one of them.



Are there any overhead considerations to take into account when doing this or does Swift optimise away all these unused values at compile time?










share|improve this question


























    1















    If I create a Struct with a function like...



    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




    Does Swift do any optimisation on doing something like...



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


    Because the setting function creates a copy and changes the copy each time you are essentially creating a new Struct over and over and over and then throwing away all but one of them.



    Are there any overhead considerations to take into account when doing this or does Swift optimise away all these unused values at compile time?










    share|improve this question
























      1












      1








      1








      If I create a Struct with a function like...



      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




      Does Swift do any optimisation on doing something like...



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


      Because the setting function creates a copy and changes the copy each time you are essentially creating a new Struct over and over and over and then throwing away all but one of them.



      Are there any overhead considerations to take into account when doing this or does Swift optimise away all these unused values at compile time?










      share|improve this question














      If I create a Struct with a function like...



      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




      Does Swift do any optimisation on doing something like...



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


      Because the setting function creates a copy and changes the copy each time you are essentially creating a new Struct over and over and over and then throwing away all but one of them.



      Are there any overhead considerations to take into account when doing this or does Swift optimise away all these unused values at compile time?







      swift optimization compilation






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 12 '18 at 17:29









      FogmeisterFogmeister

      56k27151242




      56k27151242






















          1 Answer
          1






          active

          oldest

          votes


















          6














          No, this isn't optimized. It will make a new copy for every call. It's hard to imagine how the optimizer would work this out to avoid the copies, but the wizards that write the optimizers have fooled me before. But as with most optimizer questions we don't have to guess what it does. We can look.



          I slightly rewrote this code to get rid of the optionals (which just complicate things slightly without changing the question).



          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")


          And then compiled it to SIL with optimizations:



          swiftc -O -emit-sil x.swift


          The setting method becomes this:



          // 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'


          Of particular interest is this section:



          %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


          As expected, a new copy is created every time you call setting.



          IMO, the better approach in Swift is this:



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


          This optimizes to the following (plus my annotations):



          // 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'


          What you'll notice here is that the closure execution has been completely optimized out. The compiler was able to reduce "Fogmeister" and "yesterday" to their tagged-string values, and reduce this entire block into a single init call (at %22) because it noticed I was setting all the values. That's amazing.






          share|improve this answer




















          • 1





            Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

            – Fogmeister
            Nov 12 '18 at 18:22











          Your Answer






          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "1"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader:
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          ,
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );













          draft saved

          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53267232%2fdoes-swift-optimise-chained-creation-and-copy-of-structs%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          6














          No, this isn't optimized. It will make a new copy for every call. It's hard to imagine how the optimizer would work this out to avoid the copies, but the wizards that write the optimizers have fooled me before. But as with most optimizer questions we don't have to guess what it does. We can look.



          I slightly rewrote this code to get rid of the optionals (which just complicate things slightly without changing the question).



          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")


          And then compiled it to SIL with optimizations:



          swiftc -O -emit-sil x.swift


          The setting method becomes this:



          // 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'


          Of particular interest is this section:



          %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


          As expected, a new copy is created every time you call setting.



          IMO, the better approach in Swift is this:



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


          This optimizes to the following (plus my annotations):



          // 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'


          What you'll notice here is that the closure execution has been completely optimized out. The compiler was able to reduce "Fogmeister" and "yesterday" to their tagged-string values, and reduce this entire block into a single init call (at %22) because it noticed I was setting all the values. That's amazing.






          share|improve this answer




















          • 1





            Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

            – Fogmeister
            Nov 12 '18 at 18:22
















          6














          No, this isn't optimized. It will make a new copy for every call. It's hard to imagine how the optimizer would work this out to avoid the copies, but the wizards that write the optimizers have fooled me before. But as with most optimizer questions we don't have to guess what it does. We can look.



          I slightly rewrote this code to get rid of the optionals (which just complicate things slightly without changing the question).



          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")


          And then compiled it to SIL with optimizations:



          swiftc -O -emit-sil x.swift


          The setting method becomes this:



          // 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'


          Of particular interest is this section:



          %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


          As expected, a new copy is created every time you call setting.



          IMO, the better approach in Swift is this:



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


          This optimizes to the following (plus my annotations):



          // 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'


          What you'll notice here is that the closure execution has been completely optimized out. The compiler was able to reduce "Fogmeister" and "yesterday" to their tagged-string values, and reduce this entire block into a single init call (at %22) because it noticed I was setting all the values. That's amazing.






          share|improve this answer




















          • 1





            Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

            – Fogmeister
            Nov 12 '18 at 18:22














          6












          6








          6







          No, this isn't optimized. It will make a new copy for every call. It's hard to imagine how the optimizer would work this out to avoid the copies, but the wizards that write the optimizers have fooled me before. But as with most optimizer questions we don't have to guess what it does. We can look.



          I slightly rewrote this code to get rid of the optionals (which just complicate things slightly without changing the question).



          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")


          And then compiled it to SIL with optimizations:



          swiftc -O -emit-sil x.swift


          The setting method becomes this:



          // 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'


          Of particular interest is this section:



          %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


          As expected, a new copy is created every time you call setting.



          IMO, the better approach in Swift is this:



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


          This optimizes to the following (plus my annotations):



          // 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'


          What you'll notice here is that the closure execution has been completely optimized out. The compiler was able to reduce "Fogmeister" and "yesterday" to their tagged-string values, and reduce this entire block into a single init call (at %22) because it noticed I was setting all the values. That's amazing.






          share|improve this answer















          No, this isn't optimized. It will make a new copy for every call. It's hard to imagine how the optimizer would work this out to avoid the copies, but the wizards that write the optimizers have fooled me before. But as with most optimizer questions we don't have to guess what it does. We can look.



          I slightly rewrote this code to get rid of the optionals (which just complicate things slightly without changing the question).



          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")


          And then compiled it to SIL with optimizations:



          swiftc -O -emit-sil x.swift


          The setting method becomes this:



          // 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'


          Of particular interest is this section:



          %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


          As expected, a new copy is created every time you call setting.



          IMO, the better approach in Swift is this:



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


          This optimizes to the following (plus my annotations):



          // 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'


          What you'll notice here is that the closure execution has been completely optimized out. The compiler was able to reduce "Fogmeister" and "yesterday" to their tagged-string values, and reduce this entire block into a single init call (at %22) because it noticed I was setting all the values. That's amazing.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 12 '18 at 18:14

























          answered Nov 12 '18 at 18:00









          Rob NapierRob Napier

          200k28296422




          200k28296422







          • 1





            Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

            – Fogmeister
            Nov 12 '18 at 18:22













          • 1





            Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

            – Fogmeister
            Nov 12 '18 at 18:22








          1




          1





          Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

          – Fogmeister
          Nov 12 '18 at 18:22






          Awesome, thanks :D I didn't know about compiling that way but just done it myself to have a look into it a bit more. Interesting, thanks.

          – Fogmeister
          Nov 12 '18 at 18:22


















          draft saved

          draft discarded
















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid


          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.

          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53267232%2fdoes-swift-optimise-chained-creation-and-copy-of-structs%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Kleinkühnau

          Makov (Slowakei)

          Deutsches Schauspielhaus