Есть ли различия в скорости между статической, табличной и message dispatch диспетчеризацией
Что проверяют
Понимаете ли вы, как Swift выбирает реализацию метода, и знаете ли, чем за это платится. Вопрос из тех, что отделяют глубокое знание языка от прикладного.
Как отвечать
Да, различия есть, и они идут в таком порядке от быстрого к медленному:
- статическая (прямая): адрес метода известен на этапе компиляции, вызов уходит напрямую и может быть встроен компилятором. Так вызываются методы структур и перечислений,
final,private,static, а также всё в модуле, собранном с оптимизацией целиком; - табличная: адрес берётся из таблицы. У классов это таблица виртуальных методов, у протоколов таблица свидетелей. Один лишний переход по указателю, встраивание обычно невозможно;
- через сообщения: механизм Objective-C с
objc_msgSend, поиск по имени селектора в рантайме с кешированием. Самый медленный, зато позволяет подмену методов и работу с@objc dynamic, KVO и селекторами.
Что определяет, какая будет использована: тип (значимые типы всегда статически), модификаторы (final, private), принадлежность к Objective-C (@objc, наследники NSObject), расширения (методы в расширении класса без @objc диспетчеризуются статически, и это частая причина «почему не вызвался переопределённый метод»), а также оптимизация всего модуля, которая позволяет компилятору доказать, что подкласса нет, и превратить табличный вызов в прямой.
Практический вывод: разница измеряется наносекундами и в обычном коде незаметна, но в горячих циклах и в отрисовке она видна, поэтому final на классах, не предназначенных для наследования, это дешёвая и полезная привычка.
Где спотыкаются
Переопределяют метод в расширении класса и не понимают, почему вызывается версия базового класса.
Считают, что протокол всегда означает динамический вызов: с обобщёнными функциями компилятор часто специализирует код и вызывает напрямую.
Оптимизируют диспетчеризацию до профилирования, хотя узкое место обычно в другом месте.