Roadmap for Improving Swift Performance Predictability
Joe Groff (via David Smith):
For these reasons, we think it makes sense to change the the language rules to follow what is most users’ intuition, while still giving us the flexibility to optimize in important cases. Rather than say that releases on variables can happen literally anywhere, we will say that releases are anchored to the end of the variable’s scope, and that operations such as accessing a weak reference, using pointers, or calling into external functions, act as deinitialization barriers that limit the optimizer’s ability to shorten variable lifetimes. The upcoming proposal will go into more detail about what exactly anchoring means, and what constitutes a barrier, but in our experiments, this model provides much more predictable behavior and greatly reduces the need for things like
withExtendedLifetime
in common usage patterns, without sacrificing much performance in optimized builds.[…]
By making the transfer of ownership explicit with
move
, we can guarantee that the lifetime of thevalues
argument is ended at the point we expect. If its lifetime can’t be ended at that point, because there are more uses of the variable later on in its scope, or because it’s not a local variable, then the compiler can raise errors explaining why. Sincevalues
is no longer active,self.values
is the only reference remaining in this scope, and thesort
method won’t trigger an unnecessary copy-on-write.[…]
In practice, it follows some heuristic rules:
- Most regular function arguments are borrowed.
- Arguments to
init
are consumed, as is thenewValue
passed to aset
operation.The motivation for these rules is that initializers and setters are more likely to use their arguments to construct a value, or modify an existing value, so we want to allow initializers and setters to move their arguments into the result value without additional copies, retains, or releases. These rules are a good starting point, but we may want to override the default argument conventions to minimize ARC and copies. For instance, the
append
method onArray
would also benefit from consuming its argument so that the new values can be forwarded into the data structure, and so would any other similar method that inserts a value into an existing data structure. We can add a new argument modifier to put theconsuming
convention in developer control[…][…]
A normal function stops executing once it’s returned, so normal function return values must have independent ownership from their arguments; a coroutine, on the other hand, keeps executing, and keeps its arguments alive, after yielding its result until the coroutine is resumed to completion. This allows for coroutines to provide access to their yielded values in-place without additional copies, so types can use them to implement custom logic for properties and subscripts without giving up the in-place mutation abilities of stored properties. These accessors are already implemented in the compiler under the internal names
_read
and_modify
, and the standard library has experimented extensively with these features and found them very useful, allowing the standard collection types likeArray
,Dictionary
, andSet
to implement subscript operations that allow for efficient in-place mutation of their underlying data structures, without triggering unnecessary copy-on-write overhead when data structures are nested within one another.[…]
When working with deep object graphs, it’s natural to want to assign a local variable to a property deeply nested within the graph[…] As above, we really want to make a local variable, that asserts exclusive access to the value being modified for the scope of the variable, allowing us to mutate it in-place without repeating the access sequence to get to it[…]
This looks great. A related improvement I’d like to see is a way to have function arguments that bypass ARC. For example, if I’m sorting a large array, it’s wasteful to have to retain and release the elements each time they’re passed to a comparator when they’re already owned by the array itself (which the comparator is not modifying). The same applies for iteration over a collection, or even a tree, that doesn’t change change its structure.
Previously:
- On the Road to Swift 6
- Swift Evolution Pitch: Modify Accessors
- Fast Safe Mutable State in Swift 5
- Why Swift’s Copy-on-Write Is Safe
- Swift Ownership Manifesto
Update (2021-12-24): Joe Groff:
Your “pass array elements to a comparator function” example should already not require any r/r traffic today, since Array implements
read
accessors on its subscript, and functions borrow their arguments by default
So I’m not sure why I was seeing ARC overhead before.
1 Comment RSS · Twitter
> So I’m not sure why I was seeing ARC overhead before.
Maybe you did your tests before introduction of the 'read' accessor ?