A roadmap for improving Swift performance predictability: ARC improvements and ownership control

A few points:

  • Naturally, as someone who's struggled to wring performance from high-level Swift code, I'm in favor of the intent of this proposal.

  • As you might predict, though, I have concerns about the approach.

  • IMO guaranteeing the lifetime of locals until end-of-scope is one of the worst design tradeoffs in C++. The “RAII trick” of attaching cleanups to the lifetimes of locals is not inherently better than defer—to the contrary, it's only an indirect expression of intent—and requiring explicit move() on last use (or penalizing performance when it is omitted) for the convenience of low-level code that exposes pointers or interoperates with C/C++ seems like an inversion of design priorities. Do we really believe this change is consistent with the spirit of Swift?

    At Google, the explicit moving of constructor arguments, exactly analogous to the SortedArray example in the pitch, is either in the C++ coding guidelines or routinely inserted by linters (I don't remember which). In other words, it is standard boilerplate. Of course the initializer case is the one that's easy to remember and get right, but to get it right in the many other cases where it matters is quite error prone and requires vigilance from programmers. To ask programmers for extra effort in the case of unsafe code, where they already have to exercise vigilance, and to run high-level code as efficiently as possible, seems much more consistent with the design philosophy of Swift.

  • It seems a bit premature to make other ARC changes in the name of performance predictability while semantic ARC is (IIUC) as yet not-fully-realized. Swift's performance and performance predictability has always been hobbled by an ARC implementation that doesn't work at -O0 and tries to do its job based on a fraction of the information available in the source. Shouldn't that work be carried through to its conclusion so we can see what the actual remaining problems are? /cc @Michael_Gottesman

  • The problem of withXXX { ... } pyramids-of-doom exists even if we eliminate the need for withExtendedLifetime, and ought to be addressed with a language feature. In fact I proposed one to the Swift team when I was working on SwiftUI for completely different reasons. Just as we see here, these pyramids come up often and tend to drive the introduction of mitigating language changes, which after all are expensive.

  • The complexity of what's being proposed here seems quite significant and, at least in part, easily avoided. Observe that the cases where you want to pass arguments by consuming them are exactly those where the argument needs to escape. Can't we leverage this fact to avoid introducing consuming, nonConsuming, @escaping, and @nonEscaping as separate concepts?

11 Likes