Large Structs, and COW.


(Daniel Tartaglia) #1

If I have a large struct with lots of sub-structs and I assign to just one field of one of the sub-structs, will the system make a deep copy of the struct or a shallow copy where the unmodified portions of the object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new object?

I’m worried about performance when making small changes to large objects.

Thanks,
Daniel T.


(Joe Groff) #2

Semantically, vl2 is always a distinct value, though how that ends up manifesting depends on the optimizer. By default, struct fields are stored in-line, like C structs, so sizeof(VeryLarge) will be sizeof(SubStruct) * 3 + sizeof(lots of other stuff), and a copy will be a full copy. If you want copy-on-write behavior, you currently have to implement it yourself, or build from already-implemented COW value types like Array. When structs pass a certain size threshold, the calling convention changes over to passing and returning them indirectly, though 'bar' will still naively introduce a copy to transfer the modified value from the argument to the result buffer. However, in optimized builds, if bar is inlined, then I'd expect this to ultimately reduce down to an in-place modification without copies, since vl1 is never used after assigning vl2.

-Joe

···

On Jan 22, 2016, at 11:22 AM, Daniel Tartaglia via swift-users <swift-users@swift.org> wrote:

If I have a large struct with lots of sub-structs and I assign to just one field of one of the sub-structs, will the system make a deep copy of the struct or a shallow copy where the unmodified portions of the object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new object?

I’m worried about performance when making small changes to large objects.


(Dave Abrahams) #3

If I have a large struct with lots of sub-structs and I assign to just
one field of one of the sub-structs, will the system make a deep copy
of the struct or a shallow copy where the unmodified portions of the
object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new
object?

It will be entirely distinct memory, unless SubStruct was explicitly
built to use CoW or consists of parts that were built that way
(e.g. arrays).

I’m worried about performance when making small changes to large objects.

But that's a different issue, because Swift will in fact make in-place
mutations in most cases. In the above code you're explicitly saying,
"don't change vl1; make a copy and change that." If you just say
vl1.subStructA.a = 5, there's normally no reason to worry.

···

on Fri Jan 22 2016, Daniel Tartaglia <swift-users-AT-swift.org> wrote:

--
-Dave


(Daniel Tartaglia) #4

Thanks for the reply. I’m asking because I am considering using a Redux system for an app I’m working on. One of the basic principles of Redux is that the entire app state be held in one struct, and I was worried about the system making a deep copy of the state struct every time I change any little variable.

···

On Jan 22, 2016, at 4:35 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 22, 2016, at 11:22 AM, Daniel Tartaglia via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

If I have a large struct with lots of sub-structs and I assign to just one field of one of the sub-structs, will the system make a deep copy of the struct or a shallow copy where the unmodified portions of the object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new object?

I’m worried about performance when making small changes to large objects.

Semantically, vl2 is always a distinct value, though how that ends up manifesting depends on the optimizer. By default, struct fields are stored in-line, like C structs, so sizeof(VeryLarge) will be sizeof(SubStruct) * 3 + sizeof(lots of other stuff), and a copy will be a full copy. If you want copy-on-write behavior, you currently have to implement it yourself, or build from already-implemented COW value types like Array. When structs pass a certain size threshold, the calling convention changes over to passing and returning them indirectly, though 'bar' will still naively introduce a copy to transfer the modified value from the argument to the result buffer. However, in optimized builds, if bar is inlined, then I'd expect this to ultimately reduce down to an in-place modification without copies, since vl1 is never used after assigning vl2.


(Joe Groff) #5

Copy-on-write storage is one of many use cases I had in mind for my property behaviors feature, currently under discussion over on swift-evolution. It would make it possible to declare a property with copy-on-write, out-of-line storage using an "indirect" behavior:

struct VeryLargeCOW {
  var [indirect] subStructA = SubStruct()
  var [indirect] subStructB = SubStruct()
  var [indirect] subStructC = SubStruct()
}

Today, you could manually simulate this by placing your fields in one-element arrays:

struct VeryLargeCOW {
  private var _subStructA: [SubStruct]
  var subStructA: SubStruct {
    get { return _subStructA[0] }
    set { _subStructA = newValue }
  }
}

-Joe

···

On Jan 22, 2016, at 1:52 PM, Daniel Tartaglia <danielt1263@gmail.com> wrote:

On Jan 22, 2016, at 4:35 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 22, 2016, at 11:22 AM, Daniel Tartaglia via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

If I have a large struct with lots of sub-structs and I assign to just one field of one of the sub-structs, will the system make a deep copy of the struct or a shallow copy where the unmodified portions of the object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new object?

I’m worried about performance when making small changes to large objects.

Semantically, vl2 is always a distinct value, though how that ends up manifesting depends on the optimizer. By default, struct fields are stored in-line, like C structs, so sizeof(VeryLarge) will be sizeof(SubStruct) * 3 + sizeof(lots of other stuff), and a copy will be a full copy. If you want copy-on-write behavior, you currently have to implement it yourself, or build from already-implemented COW value types like Array. When structs pass a certain size threshold, the calling convention changes over to passing and returning them indirectly, though 'bar' will still naively introduce a copy to transfer the modified value from the argument to the result buffer. However, in optimized builds, if bar is inlined, then I'd expect this to ultimately reduce down to an in-place modification without copies, since vl1 is never used after assigning vl2.

Thanks for the reply. I’m asking because I am considering using a Redux system for an app I’m working on. One of the basic principles of Redux is that the entire app state be held in one struct, and I was worried about the system making a deep copy of the state struct every time I change any little variable.


(Rudolf Adamkovič) #6

Great question!

I'm using Redux in my Swift app too! My state struct is tiny though.

Anyway, just wanted to say hello and welcome to the Redux club. :slight_smile:

R+

···

Sent from my iPhone

On 22 Jan 2016, at 22:52, Daniel Tartaglia via swift-users <swift-users@swift.org> wrote:

On Jan 22, 2016, at 4:35 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 22, 2016, at 11:22 AM, Daniel Tartaglia via swift-users <swift-users@swift.org> wrote:

If I have a large struct with lots of sub-structs and I assign to just one field of one of the sub-structs, will the system make a deep copy of the struct or a shallow copy where the unmodified portions of the object still point the same memory as the original struct?

In other words, given this code:

struct SubStruct {
    var a: Int = 0
    var b: Int = 0
}

struct VeryLarge {
    var subStructA = SubStruct()
    var subStructB = SubStruct()
    var subStructC = SubStruct()
    // lots of other stuff
}

func bar(var vl: VeryLarge) -> VeryLarge {
    vl.subStructA.a = 5
    return vl
}

let vl1 = VeryLarge()
let vl2 = bar(vl1)

Will vl2.subStructB be a copy of vl1.subStructB, or an entirely new object?

I’m worried about performance when making small changes to large objects.

Semantically, vl2 is always a distinct value, though how that ends up manifesting depends on the optimizer. By default, struct fields are stored in-line, like C structs, so sizeof(VeryLarge) will be sizeof(SubStruct) * 3 + sizeof(lots of other stuff), and a copy will be a full copy. If you want copy-on-write behavior, you currently have to implement it yourself, or build from already-implemented COW value types like Array. When structs pass a certain size threshold, the calling convention changes over to passing and returning them indirectly, though 'bar' will still naively introduce a copy to transfer the modified value from the argument to the result buffer. However, in optimized builds, if bar is inlined, then I'd expect this to ultimately reduce down to an in-place modification without copies, since vl1 is never used after assigning vl2.

Thanks for the reply. I’m asking because I am considering using a Redux system for an app I’m working on. One of the basic principles of Redux is that the entire app state be held in one struct, and I was worried about the system making a deep copy of the state struct every time I change any little variable.

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users