@owned vs @guaranteed convention for 'self' in nonmutating value type methods


(Joe Groff) #1

For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

-Joe


(Michael Gottesman) #2

For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression.

I guess you are implying here that we would be forwarding along self in the complicated computation? Wouldn't we at most have one retain/release for the expression on the value due to SILGen peepholes/SGFContexts (maybe my memory is wrong)?

Using +0 also prevents us from reusing self's resources

Can you elaborate?

and doing in-place mutation if it's uniquely referenced

I thought we were talking about non-mutating value type methods? (I am confused). Can you elaborate here?

, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

We need to be very careful here. We saw large reductions in the number of dynamic retains/releases at -Onone and significant improvements at -O across many benchmarks.

···

On Jan 18, 2016, at 11:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

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


(John McCall) #3

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

John.

···

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).


(Joe Groff) #4

For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression.

I guess you are implying here that we would be forwarding along self in the complicated computation? Wouldn't we at most have one retain/release for the expression on the value due to SILGen peepholes/SGFContexts (maybe my memory is wrong)?

Yeah.

Using +0 also prevents us from reusing self's resources

Can you elaborate?

and doing in-place mutation if it's uniquely referenced

I thought we were talking about non-mutating value type methods? (I am confused). Can you elaborate here?

A nonmutating operation on an Array can mutate its 'self' parameter's buffer to build its return value if the parameter is uniquely referenced, for instance:

extension Array {
  func appending(x: Array) -> Array {
    var myself = self // should take self's refcount
    if isUniquelyReferenced(&myself.buffer) {
      myself.append(x)
      return myself
    }
    ...
  }
}

The effect is still semantically nonmutating, but we get the benefits of in-place mutation.

, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

We need to be very careful here. We saw large reductions in the number of dynamic retains/releases at -Onone and significant improvements at -O across many benchmarks.

I know we've done all-or-nothing tests for +0 vs +1. I think it'd be worthwhile to experiment with selectively using +1 for value type methods and seeing what the impact is. My hypothesis is that most value type 'self's are effectively forwarded and can be more efficiently callee-consumed than caller-guaranteed.

-Joe

···

On Jan 18, 2016, at 8:47 PM, Michael Gottesman <mgottesman@apple.com> wrote:

On Jan 18, 2016, at 11:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

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


(Joe Groff) #5

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.

-Joe

···

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.


(Michael Gottesman) #6

For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

TBH if we do that I would rather us take this 1 step further and allow any function/method to opt-in to a different calling convention for specific parameters.

Michael

···

On Jan 19, 2016, at 11:53 AM, John McCall via swift-dev <swift-dev@swift.org> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

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


(John McCall) #7

Sure, but we’re talking about very narrow heuristics for changing the defaults here. At most, maybe your Self rule, although there are conspicuous cases (func clone() -> Self) where it would not be appropriate.

John.

···

On Jan 19, 2016, at 10:05 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.


(Michael Gottesman) #8

For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

TBH if we do that I would rather us take this 1 step further and allow any function/method to opt-in to a different calling convention for specific parameters.

Resilience makes this even more important since we can not perform the @owned -> @guaranteed optimization over resilience boundaries.

···

On Jan 19, 2016, at 1:32 PM, Michael Gottesman via swift-dev <swift-dev@swift.org> wrote:

On Jan 19, 2016, at 11:53 AM, John McCall via swift-dev <swift-dev@swift.org> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:

Michael

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

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


(John McCall) #9

Yes, I think that would also be reasonable.

John.

···

On Jan 19, 2016, at 11:32 AM, Michael Gottesman <mgottesman@apple.com> wrote:

On Jan 19, 2016, at 11:53 AM, John McCall via swift-dev <swift-dev@swift.org> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

TBH if we do that I would rather us take this 1 step further and allow any function/method to opt-in to a different calling convention for specific parameters.


(Joe Groff) #10

It's also worth investigating whether it's profitable to apply to all nonmutating value type methods. I think nonmutating 'self' is more likely to be consumed in general than it is for class or mutating methods.

-Joe

···

On Jan 19, 2016, at 10:29 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 10:05 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.

Sure, but we’re talking about very narrow heuristics for changing the defaults here. At most, maybe your Self rule, although there are conspicuous cases (func clone() -> Self) where it would not be appropriate.


(John McCall) #11

Are you imagining, like, a heroic Array.map implementation that applies the transform in-place? Because honestly I think there are very few nonmutating operations that would actually consume self.

John.

···

On Jan 19, 2016, at 10:34 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 10:29 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 10:05 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.

Sure, but we’re talking about very narrow heuristics for changing the defaults here. At most, maybe your Self rule, although there are conspicuous cases (func clone() -> Self) where it would not be appropriate.

It's also worth investigating whether it's profitable to apply to all nonmutating value type methods. I think nonmutating 'self' is more likely to be consumed in general than it is for class or mutating methods.


(Joe Groff) #12

Almost every operation with an efficient in-place form for uniquely-referenced mutation could also use that in-place variant for nonmutating operations—nonmutating variants of appending, removing, uppercaseString, etc. could all benefit.

-Joe

···

On Jan 19, 2016, at 10:59 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 10:34 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 10:29 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 10:05 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.

Sure, but we’re talking about very narrow heuristics for changing the defaults here. At most, maybe your Self rule, although there are conspicuous cases (func clone() -> Self) where it would not be appropriate.

It's also worth investigating whether it's profitable to apply to all nonmutating value type methods. I think nonmutating 'self' is more likely to be consumed in general than it is for class or mutating methods.

Are you imagining, like, a heroic Array.map implementation that applies the transform in-place? Because honestly I think there are very few nonmutating operations that would actually consume self.


(John McCall) #13

1. Many of the in-place proposals have discussed having special optimization rules for turning functional-style operations to in-place ones. I don’t know where those stand, but it’s something I think we’d want to pursue directly.

2. I don’t think those operations dominate the set of all nonmutating operations on value types.

John.

···

On Jan 19, 2016, at 11:04 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 10:59 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 19, 2016, at 10:34 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 10:29 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 19, 2016, at 10:05 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 9:53 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 18, 2016, at 9:28 AM, Joe Groff via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
For Swift 2, we changed the default reference counting convention for 'self' parameters from +1 "owned" (caller retains, callee releases) to +0 "guaranteed" (caller retains and releases). This makes a lot of sense for class methods, since it's common to invoke a number of methods on the same object in turn. Similarly for value types, it's common to want to perform a bunch of mutations in a row, but we get a "+0"-like convention naturally due to the way 'inout' works. For nonmutating value type operations, it's a bit less clear-cut—a pure operation is more likely to appear once as part of a larger expression. Using +0 also prevents us from reusing self's resources and doing in-place mutation if it's uniquely referenced, which is an extremely useful optimization for a number of operations on strings and containers. We may want to consider whether +1 is a better default, if not for all nonmutating value type methods, maybe some subset where inplace mutation is likely to be profitable. One possible heuristic would be to look at whether a method returns the Self type (possibly including tuples and fragile structs containing Self, or different instantiations of the same Self type).

I feel like the right language solution here is probably just to add attributes to allow methods to opt-in to a different default self convention.

We could do that, but we should still make sure the defaults give the best overall system performance and optimization opportunity.

Sure, but we’re talking about very narrow heuristics for changing the defaults here. At most, maybe your Self rule, although there are conspicuous cases (func clone() -> Self) where it would not be appropriate.

It's also worth investigating whether it's profitable to apply to all nonmutating value type methods. I think nonmutating 'self' is more likely to be consumed in general than it is for class or mutating methods.

Are you imagining, like, a heroic Array.map implementation that applies the transform in-place? Because honestly I think there are very few nonmutating operations that would actually consume self.

Almost every operation with an efficient in-place form for uniquely-referenced mutation could also use that in-place variant for nonmutating operations—nonmutating variants of appending, removing, uppercaseString, etc. could all benefit.