Passing value types or members of value types?


(^) #1

If I have a “large” struct like

struct Vertex
{
    let x:Double, y:Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

and I want to pass it to a function without modifying it like

func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double
{
    return v2.x - v1.x + v2.y - v1.y
}

Will the entire struct Vertex get copied and pushed onto the stack, or will
only the relevant members be passed to the function?

In other words, does the compiler know to optimize this call down to

func taxicab_distance(v2x:Double, v1x:Double, v2y:Double, v1y:Double) ->
Double
{
    return v2x - v1x + v2y - v1y
}

?


(Brent Royal-Gordon) #2

When passed as an argument, a struct is recursively broken up into individual primitives, and each of those is passed in separate arguments. (The primitive types in question are mostly special "builtin" types which are wrapped by standard library types like `Int` and `Double`.) As far as I'm aware, there is no optimization that removes unused parts of the struct. That means this would be passed as something like:

func taxicab_distance(_ v1.x._value: Builtin.Float64, _ v1.y._value: Builtin.Float64, _ v1.r._value: Builtin.Int64, _ v1.g._value: Builtin.Int64, _ v1.b._value: Builtin.Int64, _ v1.s._value: Builtin.Float64, _ v1.t._value: Builtin.Float64, _ v1.selected._value: Builtin.Int1, _ v2.x._value: Builtin.Float64, _ v2.y._value: Builtin.Float64, _ v2.r._value: Builtin.Int64, _ v2.g._value: Builtin.Int64, _ v2.b._value: Builtin.Int64, _ v2.s._value: Builtin.Float64, _ v2.t._value: Builtin.Float64, _ v2.selected._value: Builtin.Int1) -> Builtin.Float64 { … }

Because of this, it may sometimes make sense to convert large structs into copy-on-write types by hand—basically, make a struct which wraps an inner class type, guarding all mutations with an `isUniquelyReferenced(_:)` check. (You're right that there's no implicit copy-on-write behavior in structs currently—when a struct has copy-on-write behavior, it has been manually implemented that way.)

Note that this may not always be the case: There's a built-in copy-on-write optimization which will soon come to instances which have been cast to a protocol type, and the "resilient" structs planned for a future version of Swift will probably be passed by reference in some way. But I believe that's the state of things in Swift 3.

···

On May 6, 2017, at 9:33 PM, Kelvin Ma via swift-users <swift-users@swift.org> wrote:

If I have a “large” struct like

struct Vertex
{
    let x:Double, y:Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

and I want to pass it to a function without modifying it like

func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double
{
    return v2.x - v1.x + v2.y - v1.y
}

Will the entire struct Vertex get copied and pushed onto the stack, or will only the relevant members be passed to the function?

In other words, does the compiler know to optimize this call down to

func taxicab_distance(v2x:Double, v1x:Double, v2y:Double, v1y:Double) -> Double
{
    return v2x - v1x + v2y - v1y
}

--
Brent Royal-Gordon
Architechies


(^) #3

I hear that a lot but why do all the official sources sound like copy-on-write is something you have to manually implement with a class and isUniquelyReferenced if it’s not an array or a string?

···

On May 7, 2017, at 12:02 AM, Nate Birkholz <nbirkholz@gmail.com> wrote:

Let me try one more time, my stupid phone keeps changing words.

ACTUALLY, the struct is a pointer until it's written to, and is only copied on write.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:59 PM, Nate Birkholz <nbirkholz@gmail.com> wrote:

Oddly, until it's *written* you will be working with a pointer.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:33 PM, Kelvin Ma via swift-users <swift-users@swift.org> wrote:

If I have a “large” struct like

struct Vertex
{
    let x:Double, y:Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

and I want to pass it to a function without modifying it like

func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double
{
    return v2.x - v1.x + v2.y - v1.y
}

Will the entire struct Vertex get copied and pushed onto the stack, or will only the relevant members be passed to the function?

In other words, does the compiler know to optimize this call down to

func taxicab_distance(v2x:Double, v1x:Double, v2y:Double, v1y:Double) -> Double
{
    return v2x - v1x + v2y - v1y
}

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


(Nate Birkholz) #4

Classes and structs are different things. Classes are passed by reference,
structs are passed by value with copy on write.

Strings and Arrays are structs, not classes.

···

On Sat, May 6, 2017 at 10:24 PM, Kelvin Ma <kelvin13ma@gmail.com> wrote:

I hear that a lot but why do all the official sources sound like
copy-on-write is something you have to manually implement with a class and
isUniquelyReferenced if it’s not an array or a string?

On May 7, 2017, at 12:02 AM, Nate Birkholz <nbirkholz@gmail.com> wrote:

Let me try one more time, my stupid phone keeps changing words.

ACTUALLY, the struct is a pointer until it's written to, and is only
copied on write.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:59 PM, Nate Birkholz <nbirkholz@gmail.com> wrote:

Oddly, until it's *written* you will be working with a pointer.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:33 PM, Kelvin Ma via swift-users < > swift-users@swift.org> wrote:

If I have a “large” struct like

struct Vertex
{
    let x:Double, y:Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

and I want to pass it to a function without modifying it like

func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double
{
    return v2.x - v1.x + v2.y - v1.y
}

Will the entire struct Vertex get copied and pushed onto the stack, or
will only the relevant members be passed to the function?

In other words, does the compiler know to optimize this call down to

func taxicab_distance(v2x:Double, v1x:Double, v2y:Double, v1y:Double) ->
Double
{
    return v2x - v1x + v2y - v1y
}

?

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

--
Nate Birkholz


(Daniel Dunbar) #5

This is not correct. Array, and other types, implement copy-on-write themselves, but this is not true of arbitrary structs.

To answer Kelvin's question, yes, the optimizer will be able to see through that code _assuming_ it can see the definition in a way it can optimize (generally speaking, this means not something which will be dynamically dispatched, and where the implementation is available (i.e. the same file, or the same module with whole module optimization enabled)).

You can verify this by simply writing a small sample program and spot checking the assembly matches what you want, for example:

···

On May 6, 2017, at 10:27 PM, Nate Birkholz via swift-users <swift-users@swift.org> wrote:

Classes and structs are different things. Classes are passed by reference, structs are passed by value with copy on write.

--
$ cat z.swift
struct Vertex {
    let x: Double, y: Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

private func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double {
    return v2.x - v1.x + v2.y - v1.y
}

func f0(a: Vertex, b: Vertex) -> Double {
  return taxicab_distance(a, b)
}

$ swiftc -Ounchecked -S -o - z.swift | grep 'main2f0' -A8
  .private_extern __T04main2f0SdAA6VertexV1a_AD1btF
  .globl __T04main2f0SdAA6VertexV1a_AD1btF
  .p2align 4, 0x90
__T04main2f0SdAA6VertexV1a_AD1btF:
  pushq %rbp
  movq %rsp, %rbp
  movsd (%rsi), %xmm0
  subsd (%rdi), %xmm0
  addsd 8(%rsi), %xmm0
  subsd 8(%rdi), %xmm0
  popq %rbp
  retq
--

- Daniel

Strings and Arrays are structs, not classes.

On Sat, May 6, 2017 at 10:24 PM, Kelvin Ma <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:
I hear that a lot but why do all the official sources sound like copy-on-write is something you have to manually implement with a class and isUniquelyReferenced if it’s not an array or a string?

On May 7, 2017, at 12:02 AM, Nate Birkholz <nbirkholz@gmail.com <mailto:nbirkholz@gmail.com>> wrote:

Let me try one more time, my stupid phone keeps changing words.

ACTUALLY, the struct is a pointer until it's written to, and is only copied on write.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:59 PM, Nate Birkholz <nbirkholz@gmail.com <mailto:nbirkholz@gmail.com>> wrote:

Oddly, until it's *written* you will be working with a pointer.

Sent from my iPhone, please excuse brevity and errors

On May 6, 2017, at 9:33 PM, Kelvin Ma via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

If I have a “large” struct like

struct Vertex
{
    let x:Double, y:Double
    var r:Int, g:Int, b:Int
    let s:Double, t:Double
    var selected:Bool
}

and I want to pass it to a function without modifying it like

func taxicab_distance(_ v1:Vertex, _ v2:Vertex) -> Double
{
    return v2.x - v1.x + v2.y - v1.y
}

Will the entire struct Vertex get copied and pushed onto the stack, or will only the relevant members be passed to the function?

In other words, does the compiler know to optimize this call down to

func taxicab_distance(v2x:Double, v1x:Double, v2y:Double, v1y:Double) -> Double
{
    return v2x - v1x + v2y - v1y
}

?
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

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


(Brent Royal-Gordon) #6

Kelvin, you should definitely take Daniel's word over mine on whether there's an optimization for this. I believe the rest of my explanation is correct.

···

On May 6, 2017, at 10:34 PM, Daniel Dunbar via swift-users <swift-users@swift.org> wrote:

To answer Kelvin's question, yes, the optimizer will be able to see through that code _assuming_ it can see the definition in a way it can optimize

--
Brent Royal-Gordon
Architechies


(Daniel Dunbar) #7

To answer Kelvin's question, yes, the optimizer will be able to see through that code _assuming_ it can see the definition in a way it can optimize

Kelvin, you should definitely take Daniel's word over mine on whether there's an optimization for this. I believe the rest of my explanation is correct.

Actually I think yours was more accurate... while it is is true the desired optimization will often take effect (given the conditions I describe), your's was correct that this isn't happening because the function signature is taking _fewer_ arguments. Rather, the optimizations works because the compiler will tend to inline that small function and then see it can discard the unnecessary data.

Whether or not this makes it worth boxing your struct to avoid large copies probably depends on how much you need to pass the struct through call sites which would in fact need to copy the full struct, versus inlining down to something smaller.

HTH,
- Daniel

···

On May 6, 2017, at 10:39 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On May 6, 2017, at 10:34 PM, Daniel Dunbar via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

--
Brent Royal-Gordon
Architechies


(^) #8

So if I am passing the large struct to a large function, or a function that
lives in a different module (as my project is currently split into 8 or 9
modules — is that too many?), am I better off passing the members
individually instead of passing the entire struct? It seems kind of tedious
to have to write

func f(x:Double, y:Double) -> Double
{
    ....
}

let z:Double = f(x: point.x, y: point.y)

instead, and it seems like something the compiler ought to be responsible
for.

Also about boxing the struct, isn’t that the worst of both worlds? You get
the overhead of pass-by-value, the overhead of reference counting, the heap
allocation, and the heap access. Plus it’s a lot of work to manually
implement copy on write, and the copy is decided at runtime, even when it
is known that an object will or won’t be mutated at compile time.

···

On Sun, May 7, 2017 at 12:43 AM, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

On May 6, 2017, at 10:39 PM, Brent Royal-Gordon <brent@architechies.com> > wrote:

On May 6, 2017, at 10:34 PM, Daniel Dunbar via swift-users < > swift-users@swift.org> wrote:

To answer Kelvin's question, yes, the optimizer will be able to see
through that code _assuming_ it can see the definition in a way it can
optimize

Kelvin, you should definitely take Daniel's word over mine on whether
there's an optimization for this. I believe the rest of my explanation is
correct.

Actually I think yours was more accurate... while it is is true the
desired optimization will often take effect (given the conditions I
describe), your's was correct that this isn't happening because the
function signature is taking _fewer_ arguments. Rather, the optimizations
works because the compiler will tend to inline that small function and then
see it can discard the unnecessary data.

Whether or not this makes it worth boxing your struct to avoid large
copies probably depends on how much you need to pass the struct through
call sites which would in fact need to copy the full struct, versus
inlining down to something smaller.

HTH,
- Daniel

--
Brent Royal-Gordon
Architechies


(^) #9

copy on write is not intrinsic to structs, is it? I believe copy on write
is implemented in Array because Array contains a class for its storage,
which checks its own reference count when it is mutated, and String gets
the same behavior because String uses an Array as its underlying storage.

···

On Sun, May 7, 2017 at 11:57 AM, Zhao Xin <owenzx@gmail.com> wrote:

I don't tend to think all of this unless there has been already a
noticeable speed drop. So my question is, is there a noticeable drop for
you?

For your question, in my knowledge, if your function only uses/reads the
struct's value, there is no copy in memory. It is called copy on write. The
copy won't happen until you write to the copy (mutate the struct's
properties). So if your function never mutates the struct, the speed should
be very alike as you are using classes.

Zhaoxin

On Mon, May 8, 2017 at 12:21 AM, Kelvin Ma via swift-users < > swift-users@swift.org> wrote:

So if I am passing the large struct to a large function, or a function
that lives in a different module (as my project is currently split into 8
or 9 modules — is that too many?), am I better off passing the members
individually instead of passing the entire struct? It seems kind of tedious
to have to write

func f(x:Double, y:Double) -> Double
{
    ....
}

let z:Double = f(x: point.x, y: point.y)

instead, and it seems like something the compiler ought to be responsible
for.

Also about boxing the struct, isn’t that the worst of both worlds? You
get the overhead of pass-by-value, the overhead of reference counting, the
heap allocation, and the heap access. Plus it’s a lot of work to manually
implement copy on write, and the copy is decided at runtime, even when it
is known that an object will or won’t be mutated at compile time.

On Sun, May 7, 2017 at 12:43 AM, Daniel Dunbar <daniel_dunbar@apple.com> >> wrote:

On May 6, 2017, at 10:39 PM, Brent Royal-Gordon <brent@architechies.com> >>> wrote:

On May 6, 2017, at 10:34 PM, Daniel Dunbar via swift-users < >>> swift-users@swift.org> wrote:

To answer Kelvin's question, yes, the optimizer will be able to see
through that code _assuming_ it can see the definition in a way it can
optimize

Kelvin, you should definitely take Daniel's word over mine on whether
there's an optimization for this. I believe the rest of my explanation is
correct.

Actually I think yours was more accurate... while it is is true the
desired optimization will often take effect (given the conditions I
describe), your's was correct that this isn't happening because the
function signature is taking _fewer_ arguments. Rather, the optimizations
works because the compiler will tend to inline that small function and then
see it can discard the unnecessary data.

Whether or not this makes it worth boxing your struct to avoid large
copies probably depends on how much you need to pass the struct through
call sites which would in fact need to copy the full struct, versus
inlining down to something smaller.

HTH,
- Daniel

--
Brent Royal-Gordon
Architechies

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


(^) #10

i’m also curious as to whether this would be faster as a member function on the struct itself, since member functions get `self` as a pointer, right?

···

On May 7, 2017, at 11:21 AM, Kelvin Ma <kelvin13ma@gmail.com> wrote:

So if I am passing the large struct to a large function, or a function that lives in a different module (as my project is currently split into 8 or 9 modules — is that too many?), am I better off passing the members individually instead of passing the entire struct? It seems kind of tedious to have to write

func f(x:Double, y:Double) -> Double
{
    ....
}

let z:Double = f(x: point.x, y: point.y)

instead, and it seems like something the compiler ought to be responsible for.

Also about boxing the struct, isn’t that the worst of both worlds? You get the overhead of pass-by-value, the overhead of reference counting, the heap allocation, and the heap access. Plus it’s a lot of work to manually implement copy on write, and the copy is decided at runtime, even when it is known that an object will or won’t be mutated at compile time.

On Sun, May 7, 2017 at 12:43 AM, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

On May 6, 2017, at 10:39 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On May 6, 2017, at 10:34 PM, Daniel Dunbar via swift-users <swift-users@swift.org> wrote:

To answer Kelvin's question, yes, the optimizer will be able to see through that code _assuming_ it can see the definition in a way it can optimize

Kelvin, you should definitely take Daniel's word over mine on whether there's an optimization for this. I believe the rest of my explanation is correct.

Actually I think yours was more accurate... while it is is true the desired optimization will often take effect (given the conditions I describe), your's was correct that this isn't happening because the function signature is taking _fewer_ arguments. Rather, the optimizations works because the compiler will tend to inline that small function and then see it can discard the unnecessary data.

Whether or not this makes it worth boxing your struct to avoid large copies probably depends on how much you need to pass the struct through call sites which would in fact need to copy the full struct, versus inlining down to something smaller.

HTH,
- Daniel

--
Brent Royal-Gordon
Architechies