пятница, 17 февраля 2017 г.

Оптимизация типов в современных компиляторах C++



С. Макконнелл в своей замечательной книге даёт рекомендацию вида "Создавайте типы с именами, отражающими их функциональность" ("Совершенный код", с. 307) и предлагает рассмотреть выделение их в классы.

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

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

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

Для начала расскажу о тесте. Он довольно сильно синтетический: это вычисление пути, пройденного телом при условии действия на него постоянно меняющейся  силы.

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

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

А теперь самое интересное: ВСЕ рассмотренные компиляторы, судя по дизасму, убрали лишнее копирование на стеке и с этим действительно всё хорошо (и было хорошо ещё со времён выхода MSVC 2010). Но производительность всё равно вышла очень и очень разной.

На Windows 7 код тестировался на Core i5 2.67 GHz  c 4 Гб RAM, на компиляторах с включенным -O2. Лучше всего себя показала новая студия, хуже всего - почему-то MinGW.

КомпиляторСреднее время, мс
MinGW 6.2.044145
MSVC 201031306
MSVC 201526172
MSVC 2017 RC25382

Честно говоря, не понимаю, либо дефолтные ключи оптимизации у MinGW так плохи (т. е. я не сумел его правильно приготовить), либо GCC по производительности сильно отстает от более современных компиляторов.

Сходная ситуация получилась и при проверке на слабой машине с Ubuntu 16.04.2 LTS с Atom D525 1.8 MHz и 2 Гб RAM: здесь выиграл clang.

КомпиляторСреднее время, мс
clang version 3.8.0-2ubuntu4221441
gcc 5.4.0230885
gcc 7.0.0 20170113 (experimental)230344

Выводы получаются неплохими, но немного странными:
  • Такими бенчмарками современные компиляторы не проймешь и это хорошо.
  • WTF с дефолтным /O2 у GCC? Он не особо улучшается между версиями и умудряется проигрывать.
  • Clang оптимизирует чертовски хорошо.
  • Компилятор студии значительно улучшился за 5-7 лет и проблем с ним в быстродействии ожидать не стоит.
С радостью выслушаю любые комментарии на эту тему. Может есть вариант как-то подтюнить GCC?

Комментариев нет:

Отправить комментарий