[swift-evolution] [Proposal] Sealed classes by default
rjmccall at apple.com
Tue Jun 28 18:47:04 CDT 2016
> On Jun 28, 2016, at 4:01 PM, Michael Peternell <michael.peternell at gmx.at> wrote:
>> Am 29.06.2016 um 00:32 schrieb John McCall <rjmccall at apple.com>:
>> The decision to make class methods polymorphic by default was always premised on being able to undo that in obvious cases where methods are never overridden. Making a class public so that clients can use it shouldn't cause significant performance degradations just because you forgot to also write "final".
> I do care about performance. For this reason I don't want a fully dynamic language. I disagree about the "significant performance degradations just because you forgot to also write `final`". I mentioned "performance" in my original post only because it would be the only convincing argument - if there were indeed superior performance when using `final`.
> Of course, dynamic dispatch is much slower than static dispatch. But optimized code does not spend much time dispatching. If a method takes 3 seconds to complete, and from that 2 seconds are used for dynamically dispatching method calls, then I would say that it has not been optimized for performance yet. How would such a function look like? The function being dynamically dispatched should probably not be statically dispatched but inlined completely. And for the rare case that the dispatch type really makes all the difference, it's always possible to `final`ize (or `private`ize) some of the used methods.
Getters and setters are major examples of methods that (1) are very likely to be made public on a public class, (2) are very, very profitable to devirtualize and inline within the class's implementation, and (3) most programmers will never think to mark final (and are very annoying to "inline"). Now, if the program contains some cubic algorithm, then of course this level of micro performance doesn't matter much; it's at best a constant factor on the Nested Loop Of Doom. However, as a language implementor, it is not fair for me to assume that the program contains brazen inefficiencies that will swamp the impact of any decision I make; it is often the case that, after crossing off a few prominent hot-spots, programmers find themselves staring at profiles that are disconcertingly flat. And a lot of programmers feel quite rightly that they shouldn't have to massively uglify their source code with annotations just to get a little bit of performance, especially if it feels like something the language ought to just know.
I also have some responsibility to the system to try to run code as efficiently as possible, even if the programmer (be it through laziness, ignorance, or — more likely — just being over-worked) didn't put much conscious effort into optimizing it.
Anyway, I think you're underselling the advantages of devirtualization.
First, modern systems do a *lot* of indirect calls. On a typical iOS device, the indirect branch predictor is not very effective, not because its prediction algorithm is in any way inadequate but because its cache is just not large enough to deal with all the indirect branches being executed by (mostly) objc_msgSend. (I am not a hardware expert, but as I understand it, it is not easy to just make these caches bigger.) Just making a bunch of calls direct instead means those calls (1) won't miss in the cache and (2) won't contribute to thrashing the cache for other calls.
Second, devirtualization is a major enabling optimization. Many methods are quite small, especially the ones that you probably don't think of as methods, like the implicit accessors for stored properties. Being able to inline them, or even just reason about whether they change memory, can have a big impact.
Finally, the specific form of devirtualization in question here, where a method is proven to never be overridden (again, very common for accessors!), can have a significant impact separate from any calls because the method essentially no longer needs to be virtual. That is, it can be removed from the virtual method tables completely, and we may be able to completely avoid emitting it. That shrinks the size of global memory (and the binary), decrease the amount of work that has to be done at load-time, and improves locality within the virtual table.
I also think this is the right thing to do for library evolution, but yes, it is a very valuable optimization that we would otherwise struggle to do for public classes.
More information about the swift-evolution