Выбрать главу

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

Проведем эксперимент. Напишем программу, в которой циклически будут осуществляться вызовы различных типов для кода небольшого размера25. Поскольку код обработчика один и тот же, общее время, затраченное на выполнение вызова, будет прямо пропорционально времени, затраченному на организацию вызова. Запустим программу и выполним профилирование26. Результаты профилирования представлены в Табл. 14, графически они изображены на Рис. 1927.

Табл. 14. Время, затраченное на выполнение вызовов различных типов для кода небольшого размера, млс.

Рис. 19. Гистограмма результатов профилирования вызовов различных типов для кода небольшого размера

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

Посмотрим теперь результаты при использовании универсального аргумента. Если сравнить с вызовами напрямую, время выполнения ожидаемо увеличивается. Однако если для указателя на функцию и указателя на метод увеличение незначительно, то для функционального объекта и лямбда-выражения оно увеличивается настолько, что практически исчезает отличие от других способов. Теперь код обработчика не встраивается в точку вызова, и расходы на вызов функции во много раз превышают расходы на выполнение тела функции.

Модифицируем теперь код обработчика таким образом, чтобы оптимизатор не мог встроить его в точку вызова. Числовые значения замеров представлены в Табл. 15, графически они изображены на Рис. 20. Теперь картина получается иная: прямое использование функциональных объектов и лямбда-выражений не дают заметного выигрыша в производительности, а использование универсального аргумента увеличивает время выполнения незначительно.

Табл. 15. Время, затраченное на выполнение вызовов различных типов для кода большого размера, млс.

Рис. 20. Гистограмма результатов профилирования вызовов различных типов для кода большого размера

Какой код будет встраиваться в точку вызова, а какой нет? Однозначного ответа на этот вопрос дать невозможно. Алгоритмы работы оптимизатора не документируются и принимают во внимание множество факторов: количество команд в коде; количество точек вызова; наличие рекурсивных вызовов; оценка степени увеличения результирующего кода после встраивания и т. п. Самый надежный способ – посмотреть дизассемблированный код, где однозначно видно, встроен ли код обработчика в точку вызова.

Исходя из изложенного, можно сделать следующий вывод:

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

4.7. Проблемы, порождаемые шаблонами

4.7.1. Недостатки шаблонов

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

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

Сложность понимания. Код, написанный с помощью шаблонов, гораздо труднее анализировать, чем обычный. В этом можно убедиться, просмотрев, к примеру, исходный код стандартной библиотеки STL.

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

вернуться

25

Исходный код можно посмотреть здесь: .

вернуться

26

Указатели на статические методы классов в эксперименте не участвовали, потому что с точки зрения организации вызова они идентичны указателям на обычные функции. Профилирование выполнялось в среде Microsoft Visual Stidio.

вернуться

27

Если читатель попробует повторить эксперимент, то числовые значения, скорее всего, будут другими. Во-первых, они сильно зависят от используемого компилятора, точности профилировщика, производительности процессора. Во-вторых, в силу особенностей современных программно-аппаратных архитектур даже при запуске на одной и той же платформе результаты профилирования не будут повторяться, они плавают в некотором диапазоне значений. Заинтересованному читателю можно порекомендовать книгу «Крис Касперски. Техника оптимизации программ. Эффективное использование памяти», где подробно рассматривается этот вопрос.

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

вернуться

28

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