Анализ производительности

В моей предыдущей статье я объяснил различные методы диспетчеризации, доступные в языке программирования Swift. Когда мы слышим термин методы отправки, на ум приходят два слова: статический и динамический.

Я настоятельно рекомендую вам прочитать мою предыдущую статью, если вы еще этого не сделали, чтобы вы могли получить некоторые знания о методах отправки.

Но если вы думаете, что знаете, что это такое, то приступим!

Как все начиналось

Давным-давно была статья от Apple, в которой говорилось, что статическая отправка превосходит динамическую отправку. Таким образом, вы всегда должны по умолчанию использовать статическую отправку и переходить на динамическую только по мере необходимости.

Основная причина того, что мы получаем прирост производительности при использовании статической отправки по сравнению с динамической, заключается в том, что в случае статической отправки компилятор знает во время компиляции, какой метод реализации определенного класса является быть позванным. Таким образом, компилятор может настроить некоторые методы оптимизации, такие как установка некоторых флагов или, может быть, просто возможно, если это вообще возможно, преобразовать вызов в встроенную отправку (что является самым быстрым из всех)!

Принимая во внимание, что в случае динамической отправки компилятор может только выяснить, какая реализация метода должна быть вызвана для определенного класса во время выполнения, то есть метод базового класса или дочерний класс.

Это дает статической диспетчеризации прирост производительности по сравнению с динамической.

Как добиться статической рассылки

Теперь, согласно статье, есть три способа добиться статической отправки или уменьшить динамическую отправку:

  • final ключевое слово: это гарантирует, что конкретный класс никогда не может быть разделен на подклассы, конкретный метод никогда не может быть переопределен, и, следовательно, никогда не может быть динамической отправки.
  • private ключевое слово: ограничивает видимость метода или переменной для самого класса. Согласно статье:

Это позволяет компилятору находить все потенциально переопределяющие объявления. Отсутствие каких-либо таких переопределяющих объявлений позволяет компилятору автоматически определять ключевое слово final и удалять косвенные вызовы методов и доступа к свойствам.

Это означает, что всякий раз, когда мы отмечаем метод или переменную как private, компилятор выполняет поиск, если этот метод или переменная где-либо переопределяется. Если это так, это вызовет ошибку времени компиляции. Если он не обнаружит переопределенного поведения, он неявно пометит его final.

  • Техника оптимизации всего модуля: это флаг компилятора -whole-module-optimization, который теперь включен по умолчанию для новых проектов из Xcode 8. По сути, когда мы не используем этот флаг (-wmo), компилятор Swift компилирует все наши .swift файлы, которые принадлежат к одному модулю отдельно. Это ограничивает компилятор от добавления определенных оптимизаций, таких как встраивание, поскольку он будет компилировать все файлы отдельно и, таким образом, компилятор не знает, как связаны разные классы и их методы. Когда мы используем -wmo, компилятор Swift компилирует все эти .swift файлы вместе и, таким образом, может добавлять оптимизацию. Если вы хотите узнать больше о -wmo, здесь есть хорошая и подробная документация.

И наконец

Прочитав статью, я подумал только об одном - где доказательства?

Я знаю, что до сих пор мы прошли через множество теоретических концепций, но мы программисты. Мы любим код. И что может быть лучше, чем код, подтверждающий все эти концепции!

Небольшой тест

Я создал небольшой проект Тестер производительности, который представляет собой не что иное, как набор классов Swift и несколько модульных тестов. Это был единственный способ проверить производительность / скорость кода. Вы можете клонировать репозиторий и убедиться в этом сами.

Я использовал только ключевое слово final, но вы можете использовать и два других подхода. Я расскажу вам вкратце, что представляют собой компоненты проекта и как запускать тестовые примеры.

В проекте для нас важны всего два файла:

  1. StaticDispatch.swift - содержит все классы, которые мы будем использовать в наших тестовых примерах.
  2. PerformanceTesterTests.swift - присутствует в группе PerformanceTesterTests, которая является нашей группой модульных тестов и содержит наш тестовый пример.

Я добавил комментарии в оба файла, чтобы сделать его более понятным.

Чтобы запустить тестовый пример -

  • Раскомментируйте строку 36 в файле PerformanceTesterTests.swift, который принадлежит нашей статической отправке.
  • Щелкните значок ромба в строке 33, чтобы запустить тестовый пример.

Это запустит симулятор iPhone. Как только симулятор подключится к сети, он запустит тестовый пример.

  • После того, как тестовый пример завершится, вы увидите время, которое потребовалось для его выполнения, с правой стороны.

  • Он может сказать «базовая линия не зафиксирована» или что-то в этом роде, но вам нужно щелкнуть серую галочку, как на изображении (возможно, вам придется еще раз щелкнуть галочку после начальной ), при этом откроется всплывающее окно, подобное этому:

  • Теперь щелкните Edit -> Accept -> Save. Это время будет использоваться в качестве базового, и все сравнения будут проводиться с этим базовым временем, которое мы сохранили.
  • Вот и все! Прокомментируйте текущую строку (строка 36) и раскомментируйте следующую строку (помните, раскомментируйте только одну строку за раз), снова запустите тестовый пример, используя строку 33, и сами увидите разницу в производительности. Он покажет вам разницу в% между установленной нами базовой линией (наша статическая отправка) и нашим текущим тестовым примером.

Некоторые наблюдения

Итак, чтобы подвести итог, в качестве хорошей практики вы должны сначала пометить свой класс как final, а если требуется наследование, удалите ключевое слово final. Это обеспечивает оптимизацию компилятора и, следовательно, повышает производительность кода.