Does Swift optimise chained creation and copy of structs?











up vote
1
down vote

favorite












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


























    up vote
    1
    down vote

    favorite












    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
























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      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 at 17:29









      Fogmeister

      55.2k25149239




      55.2k25149239
























          1 Answer
          1






          active

          oldest

          votes

















          up vote
          4
          down vote



          accepted










          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























          • 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 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',
          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








          up vote
          4
          down vote



          accepted










          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























          • 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 at 18:22

















          up vote
          4
          down vote



          accepted










          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























          • 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 at 18:22















          up vote
          4
          down vote



          accepted







          up vote
          4
          down vote



          accepted






          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 at 18:14

























          answered Nov 12 at 18:00









          Rob Napier

          196k27289414




          196k27289414












          • 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 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 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 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 at 18:22




















           

          draft saved


          draft discarded



















































           


          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

          Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

          ComboBox Display Member on multiple fields

          Is it possible to collect Nectar points via Trainline?