`final` not being inferred when it should?

According to the optimization tips the compiler should be able to infer final for internal classes if you have WMO enabled, but even though we do compile with that, we were still able to see a notable decrease in app size / increase in performance after tagging a large number of internal classes with final throughout our app.

I thought this was weird because all of the changed classes are internal, so it sounds like the compiler should have been able to do this optimization on its own. Are there additional conditions for this to happen, like maybe not inheriting from Obj-C classes? We're compiling with -Osize.

6 Likes

Thanks for the report. Are you able to extract any examples of this happening? And just to be sure, are you building for testability (which will make internal classes behave as if public for @testable imports to work)?

It looks like we've seen similar reports internally at Apple. In those cases, it appears that the resulting assembly language for method implementations and invocations ends up the same as if the internal methods without overrides were final, but there is additional code size from metadata like method descriptors that still gets emitted, but which isn't recognized as dead and stripped later on. Does that sound like what you're seeing? Do you think the performance difference is down to the added code size or ongoing overhead from unnecessary dynamic dispatch?

1 Like

I'm going to try to confirm exactly which classes responded to the attribute to be able to provide concrete examples, but from what I understand from the diff the reduction was indeed Swift metadata being stripped from specific types but also a lot of Obj-C as well (most of the reductions come from the objc_data section of the binary). We have a lot of classes that inherit from NSObject, so I thought it may have to do with that.

I think the performance part may have just been noise from our instrumentation. I can see some symbols increased in size which I thought was the devirtualization kicking in, but since you say the assembly stayed in the same in the internal cases then it's probably me not understanding what I'm looking at. But we should be able to check that too once I get some examples.

1 Like