Understanding pass-by-value


(Ryan Lovelett) #1

struct Foo {
  init(from buffer: Data) {
     bar = integer(withBytes: Array(buffer[4..<6]))
     baz = integer(withBytes: Array(buffer[6..<8]))
     ...
  }

let d = Data(count: Int(3e+8))
let f = Foo(from: d)

Did I just make two copies of the `Data`? How would I investigate this
to understand it?

I _think_ that if I made it `inout` then it would not make a copy but
now the data buffer is mutable. I want it to be clear I'm not mutating
the buffer inside the initializer but I also want to be sure I'm not
making a copy of the buffer either.

Help?

···

--
  Ryan Lovelett
  ryan@lovelett.me


(Brent Royal-Gordon) #2

struct Foo {
init(from buffer: Data) {
    bar = integer(withBytes: Array(buffer[4..<6]))
    baz = integer(withBytes: Array(buffer[6..<8]))
    ...
}

let d = Data(count: Int(3e+8))
let f = Foo(from: d)

Did I just make two copies of the `Data`? How would I investigate this
to understand it?

Do you mean, "did I make two copies of the `Data`, one in a top-level variable named `d` and the other in a parameter named `buffer`"?

If so, then answer is "Yes, but…"

A value type like `Data` can't really hold variable-sized data like the bytes in a `Data` object. Instead, the bytes are stored in a separate object, and `Data` manages that with copy-on-write semantics. In other words, there are two copies of the `Data` instance itself, but they share a single copy of the bytes they're storing.

To illustrate more clearly, after this line:

  let d = Data(count: Int(3e+8))

You have something like this:

  > ...stack frame for top level... | +-------------------------------+
  > Data instance (d) | --------------> | ...3e+8 bytes of data... |
  > > +-------------------------------+

And then once you execute the call on this line:

  let f = Foo(from: d)

You have this:

  > ...stack frame for top level... | +-------------------------------+
  > Data instance (d) | --------------> | ...3e+8 bytes of data... |
  > > +-------------------------------+
  > ...stack frame for Foo(from:) | ^
  > Data instance (buffer) | ---------------------------------+

If `Foo(from:)` were to copy `buffer` and then mutate the copy, the bytes would be copied before the mutation occurred. But since neither `f` nor `buffer` is mutated in this code (indeed, both are in immutable variables!), that never happens here.

I _think_ that if I made it `inout` then it would not make a copy but
now the data buffer is mutable. I want it to be clear I'm not mutating
the buffer inside the initializer but I also want to be sure I'm not
making a copy of the buffer either.

That's implementation-specific. Notionally, an `inout` variable is essentially passed in by value, modified as a copy, returned to the caller, and then assigned back to the original value. In some cases that's basically what actually ends up happening. But Swift tries to optimize `inout` behavior into directly mutating the original variable, and it's often successful.

···

On Nov 4, 2016, at 5:59 AM, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Ryan Lovelett) #3

Do you mean, "did I make two copies of the `Data`, one in a top-level
variable named `d` and the other in a parameter named `buffer`"?

That is more precisely what I meant. Mutters to self: "Precision of
language, Ryan!"

That fairly completely answers my quandary. Thank you for your time and
energy.

Just out of curiosity if I looked at the SIL, would that allow me to see
any of that in action? Or would it be too opaque?


(Rien) #4

struct Foo {
init(from buffer: Data) {
    bar = integer(withBytes: Array(buffer[4..<6]))
    baz = integer(withBytes: Array(buffer[6..<8]))
    ...
}

let d = Data(count: Int(3e+8))
let f = Foo(from: d)

Did I just make two copies of the `Data`? How would I investigate this
to understand it?

I often end up “printing” the addresses or using GDB to take an inside look.
However those snippets of inside information obtained that way are not necessary useful outside the specific problem that was studied.
As Brent already hinted: the compiler will generate different code in different situations. And for an outsider it is not always easy to understand why which code was generated.
The best approach imo is to “trust” the compiler and only start such investigations if you experience performance problems.

One thing that tripped me up: if you use inout variables, the observers will be triggered once the function completes. Even if the function never changed the data referred to. (This is now documented behaviour)

I _think_ that if I made it `inout` then it would not make a copy but
now the data buffer is mutable. I want it to be clear I'm not mutating
the buffer inside the initializer but I also want to be sure I'm not
making a copy of the buffer either.

Help?

--
Ryan Lovelett
ryan@lovelett.me
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

···

On 04 Nov 2016, at 13:59, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:


(Ryan Lovelett) #5

I often end up “printing” the addresses or using GDB to take an inside
look.

That is a really simple interrogation technique I wish I had thought of
that! Thank you!

One thing that tripped me up: if you use inout variables, the observers
will be triggered once the function completes. Even if the function never
changed the data referred to. (This is now documented behaviour)

Could you provide a link to such documentation? I think that would be
interesting to read.


(Brent Royal-Gordon) #6

Maybe. What might be more directly useful is looking at the source for `Data` in Foundation:

<https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/Data.swift#L73>

You'll notice that `Data` has an internal property called `_wrapped` of type `_SwiftNSData`, which (if you jump up a few dozen lines) is a class. Since it's inside a class, the contents of that property won't be automatically copied.

Looking around a little more thoroughly, you might notice that `mutating` methods call `_applyUnmanagedMutation(_:)`: <https://github.com/apple/swift/blob/14b689dee1dcd4e5e7a8b5722ce343a57e10149d/stdlib/public/SDK/Foundation/Data.swift#L444>. If you search the codebase, you'll find the implementation of that method in a different file: <https://github.com/apple/swift/blob/c3b7709a7c4789f1ad7249d357f69509fb8be731/stdlib/public/SDK/Foundation/Boxing.swift#L173>.

The heart of the copy-on-write behavior is the call to `isKnownUniquelyReferenced(_:)`. This is a part of the standard library which is basically only used to implement copy-on-write. `isKnownUniquelyReferenced(_:)` returns `true` if your variable is the only one which has a strong reference to the object. If so, it's safe to mutate; if not, you'll need to make a copy so you don't affect any of the other instances sharing the object with you.

···

On Nov 4, 2016, at 9:45 AM, Ryan Lovelett <ryan@lovelett.me> wrote:

Just out of curiosity if I looked at the SIL, would that allow me to see
any of that in action? Or would it be too opaque?

--
Brent Royal-Gordon
Architechies


(Rien) #7

https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254

Check the last ‘note’ in the Property Observer section.
You can also follow the link in there for more.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

···

On 04 Nov 2016, at 17:48, Ryan Lovelett <ryan@lovelett.me> wrote:

I often end up “printing” the addresses or using GDB to take an inside
look.

That is a really simple interrogation technique I wish I had thought of
that! Thank you!

One thing that tripped me up: if you use inout variables, the observers
will be triggered once the function completes. Even if the function never
changed the data referred to. (This is now documented behaviour)

Could you provide a link to such documentation? I think that would be
interesting to read.


(Joe Groff) #8

There's also some deeper documentation about the accessor model in the compiler, here:

https://github.com/jckarter/swift/blob/master/docs/proposals/Accessors.rst

-Joe

···

On Nov 4, 2016, at 10:12 AM, Rien via swift-users <swift-users@swift.org> wrote:

On 04 Nov 2016, at 17:48, Ryan Lovelett <ryan@lovelett.me> wrote:

I often end up “printing” the addresses or using GDB to take an inside
look.

That is a really simple interrogation technique I wish I had thought of
that! Thank you!

One thing that tripped me up: if you use inout variables, the observers
will be triggered once the function completes. Even if the function never
changed the data referred to. (This is now documented behaviour)

Could you provide a link to such documentation? I think that would be
interesting to read.

https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254

Check the last ‘note’ in the Property Observer section.
You can also follow the link in there for more.