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
withExtendedLifetimein 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 thevaluesargument 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. Sincevaluesis no longer active,self.valuesis the only reference remaining in this scope, and thesortmethod won’t trigger an unnecessary copy-on-write.[…]
In practice, it follows some heuristic rules:
- Most regular function arguments are borrowed.
- Arguments to
initare consumed, as is thenewValuepassed to asetoperation.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
appendmethod onArraywould 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 theconsumingconvention 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
_readand_modify, and the standard library has experimented extensively with these features and found them very useful, allowing the standard collection types likeArray,Dictionary, andSetto 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
readaccessors 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 ?