3

I am trying to squeeze every last bit of performance out of my app. I try to use Structs over classes wherever possible (no state sharing, direct dispatch by default, etc etc). But my view controllers and UIView objects are obviously still classes. I want to force direct dispatch on every single one of my methods and data members - for performance reasons.

Do I need to still mark every var, let and func in my class final, or is it enough to just mark the hosting class final, in order for everything underneath it to take advantage of direct method dispatch?

In other words: It's very tedious to paste final everywhere - before each method and variable. So I'm hoping that just putting it on the class itself has the same effect of forcing direct dispatch on all class members. But I don't know how to test or verify it.

For those who are wondering what I'm talking about, check out this article: "Method Dispatch in Swift". Structs and protocol extensions give you Static Method Dispatch by default (fastest performance), but classes do not. Static methods in classes do, but I want to force Static Dispatch on all instance methods and data members.
https://www.raizlabs.com/dev/2016/12/swift-method-dispatch/

The swift language runtime documentation mentions the effect on ability to subclass, but does not describe what happens to the dispatch behavior of child members and functions of classes that are marked "final". It would be nice if everything underneath gained Static Method Dispatch without having to mark everything final individually.

final

Apply this modifier to a class or to a property, method, or subscript member of a class. It’s applied to a class to indicate that the class can’t be subclassed. It’s applied to a property, method, or subscript of a class to indicate that a class member can’t be overridden in any subclass. For an example of how to use the final attribute, see Preventing Overrides.

FranticRock
  • 3,233
  • 1
  • 31
  • 56

1 Answers1

5

Yes, just mark the type as final and its properties and methods are final, too. But if this is UIKit code (method overrides, UIKit delegate methods, etc.), that’s always going to be dispatched dynamically. It’s really only material for your own, computationally intensive code, and even then there’s a question as to whether that’s the critical issue or whether there are other issues (e.g. lookups in arrays rather than dictionaries, parallelization of complex routines, use of Accelerate or Metal of certain tasks, turning on optimized release builds, etc.).

But if you’re converting to static dispatch on code that isn’t called very frequently, the difference may be modest/unobservable.

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

I’m curious if you’ve really done enough analysis to confirm you’re in that 3%. If you have, I apologize for pointing out the obvious; it’s just that I didn’t see anything above to indicate how you determined that static dispatch was going to make a real difference. If you have a performance issue, usually going through and making everything final is unlikely to be the silver bullet to resolve the issue.


I’d refer you to WWDC videos that outline methodologies for identifying and resolve practical performance issues:

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you. Yes I realize that in practice dispatch differences may actually not be observable on code that's not heavy in terms of number of dispatches. (I would imagine that If I had a loop of 1000 to 10000 calls, the difference would probably be noticeable. That gives me an idea for a simple unit test like that). I currently don't have any serious performance issues. I am just doing a last round of optimization during the QA cycle. Also, I appreciate the links to the WWDC sessions and will definitely investigate. – FranticRock Mar 23 '19 at 00:08
  • IMHO, even 10,000 calls is unlikely to make a material change (I bet is will probably still be measured in milliseconds). I find I often need millions of calls before this sort of stuff becomes relevant. But your idea to use `measure` in unit tests is a great way to confirm. And make sure you test (a) on physical device; and (b) release/optimized builds. Using debug/unoptimized build often has huge impact on performance. But usually algorithmic changes/fixes have a far greater impact. But test, and see. Good luck! – Rob Mar 23 '19 at 00:14
  • 3
    Number of calls: 10K, 100K, 1M, 10M. Static dispatch execution times (s): 0.276, 0.304, 0.337, 0.857. Table dispatch execution times (s): 0.301, 0.305, 0.340, 0.873. OK the really interesting part came with @objc dynamic modifier (significantly slower): 0.314, 0.307, 0.519, 2.655 So with 10 million operations, we have a difference of 1.798 seconds between Dynamic and Static. But there's almost no difference between Table and Static - I'm presuming because Table calls are cached, and i'm just repeating the same method call. – FranticRock Mar 23 '19 at 01:49