Невероятно быстрые делегаты C++ - Измерение производительности

ОГЛАВЛЕНИЕ

Измерение производительности

Мы измеряли производительность вызова делегата с различными комбинациями виртуальных/невиртуальных методов, с различным числом аргументов и с различными видами наследования. Также мы измеряли производительность делегатов, связанных с функцией и статическим методом. Мы сравнили производительность FastDelegate с нашим подходом, используя компиляторы MS Visual C++ 7.1 и Intel C++ 8.0 на процессоре P4 Celeron.

В сложных случаях использование функции-заглушки может вызвать значительные непроизводительные затраты (до 5,5 раз в MSVC и до 2,4 раз в Intel). Но иногда самые быстрые делегаты оказываются медленнее (до 15% в Intel и немного на MSVC). Они всегда медленнее на статических членах и на свободных функциях. Как это возможно?

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

Наиболее быстрые делегаты вынуждены использовать соглашение о вызовах 'thiscall'. Наши делегаты могут использовать любое соглашение о вызовах (за исключением 'thiscall'), включая '__fastcall'. Это позволяет передавать до двух аргументов размера int через регистры ('thiscall' передает только указатель 'this' через ECX).

На самом деле существует простой способ сделать ваш код, основанный на делегатах, очень быстрым (если это вам действительно нужно):

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

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

Копирование и сравнение делегатов

Производительность конструктора копирования не существенна для обоих типов делегатов (в отличие от делегатов, основанных на динамическом распределении памяти, таких как boost::function). Однако наши делегаты могут копироваться немного быстрее, так как они занимают меньше места.

Наши делегаты нельзя сравнивать. Операторы сравнения не определены, так как делегат не содержит указатель на метод. Указатель на функцию-заглушку может отличаться для различных компилируемых модулей. Фактически, удаление этой возможности было основной причиной того, почему Дона Клагстона не удовлетворил мой подход.

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

Мы знаем только одну причину, почему вам может потребоваться сравнивать делегаты. Это синтаксис события, такой как в C#. Он выглядит привлекательно, но не может быть реализован без динамического распределения памяти. Более того, в C++ он может работать неверно в ряде случаев. Мы бы предложили другой механизм распространения событий, более подходящий для C++, по нашему мнению.