Форум программистов, компьютерный форум CyberForum.ru

реализация класса в .h файле хорошо или плохо? - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 35, средняя оценка - 4.69
FanOfGun
6 / 6 / 1
Регистрация: 13.10.2012
Сообщений: 101
17.08.2013, 21:48     реализация класса в .h файле хорошо или плохо? #1
все знакомые мне ide разделяют класс на два файла: .h с описанием и .cpp с кодом, но, например, в boost .hpp файлы почти всегда содержат и реализацию классов, т.е. так тоже можно. так в чем тогда разница и когда какой способ нужно применять? заранее благодарен
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
castaway
Эксперт С++
4837 / 2976 / 367
Регистрация: 10.11.2010
Сообщений: 11,008
Записей в блоге: 10
Завершенные тесты: 1
19.08.2013, 18:17     реализация класса в .h файле хорошо или плохо? #41
Ну почему же? Например, функция LTO в GCC позволяет на несколько процентов уменьшить размер исполняемого файла. Я не вижу смысла использовать эту функцию в мелких проектах, но наоборот, вижу смысл в больших. Ведь LTO не только инлайнит такие методы, но и выполняет другие оптимизации, например такие как удаление неиспользованных функций. Поскольку оптимизация выполняется на уровне линковки, то я не думаю что время сборки в целом увеличится в разы.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
19.08.2013, 19:01     реализация класса в .h файле хорошо или плохо? #42
Цитата Сообщение от Evg Посмотреть сообщение
Поднимите руку те, кто использовал режим межмодульного inline в реальных больших проектах, состоящих из десятков тысяч файлов, которые в помодульном режиме собираются несколько часов. Думаю, таких извращенцев нет и в ближайшем обозримом будущем не появится
Во-первых, при чем здесь большие проекты ? Или Вы считаете, что только те фичи имеют
право на существование, которые сертифицированы для использования на космических станциях ?
Так можно сказать, что и прекомпилированные заголовки не нужны, и разворачивание циклов, и
profile-guided optimization, и т.д.

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

Цитата Сообщение от Evg Посмотреть сообщение
При написании классов ни в коем случае не надо закладываться на подобные режимы. Нормальные люди ими мало пользуются.
А аргументы ? Забыли ?

Цитата Сообщение от Evg Посмотреть сообщение
Теперь возьмём ещё один пример. Мы поставляем библиотеку в виде "бинарник библиотеки + инклюды". Что у нас в этом случае может сделать компилятор в этом нанорежиме? Да ничего не сможет. Если мы поставляем библиотеку в виде бинарника, значит мы не хотим показывать её внутренности, значит мы не будем её компилировать в подобных режимах. А потому нужно нормально в хидера прописать все короткие inline-методы и inline-функции (внутренности которых прятать не критично, т.к. и ежу понятно, что в них написано)
Дело вкуса. Лично я предпочту отдать клиентам четко разделенные интерфейс и реализацию,
чем хидеры, содержащие частичные определения. Пусть и ценой определенных уступок.
А размазывая определения по хидерам и cpp-файлам, можно наступить на мину под названием
"нарушение ODR". Особенно в тех самых "больших" проектах. Компилятор прожует и не поморщится.

Цитата Сообщение от Evg Посмотреть сообщение
Хочется ещё раз подчеркнуть. При разработке софта выбросьте из головы эти идиотские режимы и рассчитывайте эффективность исходя из нормального помодульного режима. А всякие межмодульные inline появились не от хорошей жизни.
Наоборот. Используйте их. И вам не придется думать о том, что компилятор не сможет
где-то что-то заинлайнить только потому, что у него нет доступа к определению.
А вообще, и то и другое попахивает преждевременной оптимизацией.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
19.08.2013, 19:46     реализация класса в .h файле хорошо или плохо? #43
Цитата Сообщение от Убежденный Посмотреть сообщение
Во-первых, при чем здесь большие проекты ? Или Вы считаете, что только те фичи имеют
право на существование, которые сертифицированы для использования на космических станциях ?
На гипертрофированных примерах лучше видно. Возьмём проект из 1000 файлов *.cpp. Соберём их помодульно. Допустим, ушёл час времени. Поменяем один файл *.cpp (изменим реализацию интерфейса, что является наиболее частой операцией). Перекомпилируем. Пересборка займёт, условно говоря, несколько секунд, т.к. перекомпилироваться будет 1 файл, а не 1000. В случае режима с межмодульным инлайном при любом изменении в любом файле финальная межмодульная стадия компиляции будет работать по полной программе и займёт, условно говоря, столько же времени, сколько и полная сборка с нуля

Пока твой проект состоит из 10 файлов, разница между помодульным режимом в абсолютном времени будет небольшая. В проекте из 1000 файлов - разница будет огромная. Не говоря о том, что режим с межмодульным инлайном по времени работает далеко не линейно по отношению к количеству файлов. Увеличили количество файлов в 2 раза - время компиляции увеличится в 3, 5, 10 или хз сколько раз, но заведомо больше, чем в 2 раза. В противном случае межмодульная часть компилятора попросту работает некачественно

Цитата Сообщение от Убежденный Посмотреть сообщение
Так можно сказать, что и прекомпилированные заголовки не нужны, и разворачивание циклов, и
profile-guided optimization, и т.д.
Не надо путать оптимизации и режим работы. Раскрутка циклов - это оптимизация. Инлайн - это оптимизация. Режим IPO - это НЕ оптимизация, это режим, при котором у тебя попросту 100500 исходников склеиваются в один прозрачным для пользователя способом

Цитата Сообщение от Убежденный Посмотреть сообщение
Или поделитесь ссылкой на соответствующие исследования/статистику, или замечание ни о чем.
Конкретной ссылкой поделиться не могу. Но я с этим режимом работал задолго до того, как он появился у gcc и у интеловского компилятора. Могу даже объяснить причину, по которой этот режим вообще на свет появился:

1. (самая главная причина) Опубликовать цифры производительности. Все разработчики (будь то компиляторов, будь-то аппаратуры) публикуют цифры производительности по всяким бенчмаркам. Это стандартный узаконненный способ вешать лапшу на уши пользователям. Т.е. все замеры можно повторить и они покажут те же цифры. Но на реальных задачах (а не на бенчмарках) таких цифр с большой вероятностью не добиться. Режим межмодульного инлайна - ещё одна статья для увеличения цифр производительности, в котором компилятору можно получить бОльшее раздолье для всяких эвристических оптимизаций, которые хорошо ведут себя на бенчмарах, но плохо на реальных задачах
2. (полезная причина) Есть много старого софта, который был написан во времена, когда компиляторы были слабые, а потому никто не задумывался о том, чтобы написать софт правильно. Или программа на Си, в котором не было удобных и эффективных средств по части inline, которые есть на Си++. Может быть, что-то ещё.
3. (маркетинговая причина) Сделать режим для криворуких программистов, которые вообще не понимают (и не хотят понимать), как работает компилятор в первом приближении и как устроены машинные коды. На банере яркими буквами пишется, что, дескать, программируйте и не задумывайтесь ни о чём, наш супер-пупер межмодульный режим сделает вас абсолютно щастливыми

Цитата Сообщение от Убежденный Посмотреть сообщение
А аргументы ? Забыли ?
Про время компиляции и неудобство разработки уже говорил. Есть ещё такая вещь, как преносимость. Т.е. пока мы живём на компиляторе, в котором этот режим есть, у нас программа более-меднее производительно работает. Как только перешли на другую платформу, где этого режима нет, получили дерьмовый код.

Цитата Сообщение от Убежденный Посмотреть сообщение
А размазывая определения по хидерам и cpp-файлам, можно наступить на мину под названием
"нарушение ODR". Особенно в тех самых "больших" проектах. Компилятор прожует и не поморщится
Пример можно? Имеется в виду пример того, как наступишь, а потом долго ищешь причину

Цитата Сообщение от Убежденный Посмотреть сообщение
Наоборот. Используйте их. И вам не придется думать о том, что компилятор не сможет
где-то что-то заинлайнить только потому, что у него нет доступа к определению.
А вообще, и то и другое попахивает преждевременной оптимизацией.
"И то, и другое" - это что?
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
19.08.2013, 20:47     реализация класса в .h файле хорошо или плохо? #44
Цитата Сообщение от Evg Посмотреть сообщение
Возьмём проект из 1000 файлов *.cpp. Соберём их помодульно. Допустим, ушёл час времени. Поменяем один файл *.cpp (изменим реализацию интерфейса, что является наиболее частой операцией). Перекомпилируем. Пересборка займёт, условно говоря, несколько секунд, т.к. перекомпилироваться будет 1 файл, а не 1000. В случае режима с межмодульным инлайном при любом изменении в любом файле финальная межмодульная стадия компиляции будет работать по полной программе и займёт, условно говоря, столько же времени, сколько и полная сборка с нуля

Пока твой проект состоит из 10 файлов, разница между помодульным режимом в абсолютном времени будет небольшая. В проекте из 1000 файлов - разница будет огромная. Не говоря о том, что режим с межмодульным инлайном по времени работает далеко не линейно по отношению к количеству файлов. Увеличили количество файлов в 2 раза - время компиляции увеличится в 3, 5, 10 или хз сколько раз, но заведомо больше, чем в 2 раза. В противном случае межмодульная часть компилятора попросту работает некачественно
Скажите, как давно Вы последний раз работали с этим режимом ?
Спрашиваю, потому что написанное выше не соответствует действительности (а может, никогда
ей не соответствовало). При IPO повторная компиляция не выполняется. В Visual C++, например,
IPO - это некий промежуточный режим между компиляцией, когда код уже перенесен в промежуточное и
независимое от процессорной архитектуры представление, и компоновкой.

Время сборки увеличивается, но далеко не в такой прогрессии, как Вы указали.
Например, беру свой текущий проект: примерно 500 cpp-файлов, сборка под x86/amd64.
Общее время сборки порядка 3 минут (на Core-i5 2500). При этом треть этого времени составляет
сборка драйверов и компонентов, не относящихся к C++ и IPO. Да, и я имел дело с header-only
библиотеками, при подключении которых время сборки увеличивалось чуть ли не на порядок.
Не давая, по сути, ничего взамен. Ну кроме простоты сборки, разумеется, это вне вопросов.

Цитата Сообщение от Evg Посмотреть сообщение
Не надо путать оптимизации и режим работы. Раскрутка циклов - это оптимизация. Инлайн - это оптимизация. Режим IPO - это НЕ оптимизация, это режим, при котором у тебя попросту 100500 исходников склеиваются в один прозрачным для пользователя способом
IPO - это такая же оптимизация, как и все остальное.
Как формально (буква "О" в названии говорит сама за себя), так и по факту.
Но мы же не формалисты, нас ведь интересует практическое применение, а не навешивание ярлыков ?

Цитата Сообщение от Evg Посмотреть сообщение
Это стандартный узаконненный способ вешать лапшу на уши пользователям.
Разумеется.
А знаете, в чем прелесть IPO ? В том, что для его использования не нужно прикладывать
никаких усилий. Он просто включен по умолчанию. Думаю, большинство пользователей, особенно
начинающих, вообще не знают, что их компилятор использует IPO. И тем не менее, получают
все выгоды от него.

Цитата Сообщение от Evg Посмотреть сообщение
Режим межмодульного инлайна - ещё одна статья для увеличения цифр производительности, в котором компилятору можно получить бОльшее раздолье для всяких эвристических оптимизаций, которые хорошо ведут себя на бенчмарах, но плохо на реальных задачах
Доказательства "плохости" есть ? Если нет - значит, ни о чем.

Цитата Сообщение от Evg Посмотреть сообщение
Сделать режим для криворуких программистов, которые вообще не понимают (и не хотят понимать), как работает компилятор в первом приближении и как устроены машинные коды. На банере яркими буквами пишется, что, дескать, программируйте и не задумывайтесь ни о чём, наш супер-пупер межмодульный режим сделает вас абсолютно щастливыми
Ваша реакция понятна. Хотя я, честно говоря, ожидал что-то в духе: "оу, круто, надо будет
испытать эту штуку".

Цитата Сообщение от Evg Посмотреть сообщение
Про время компиляции и неудобство разработки уже говорил.
Время компиляции увеличивается незначительно. Не факт, что при использовании реализации в
хидерах оно было бы меньше. При прочих равных условиях. Ну а неудобство... В чем оно ?
Я могу писать код в хидерах. А могу в cpp-файлах. И, что хорошо, сгенерированный
машинный код будет практически эквивалентным. Где здесь неудобство ?
Вижу только одно "неудобство" - программисты, выросшие на современных компиляторах,
не знают что такое инлайнинг по своей сути. И как его правильно готовить. Хотя это неудобство -
оно для кого ? Для корифеев, которые ноют "вот раньше были времена, а сейчас молодежь не та" ?
Ну так может пора ему уже на свалку, этому инлайнингу, раз он не играет существенной роли в
оптимизации ?

Цитата Сообщение от Evg Посмотреть сообщение
Есть ещё такая вещь, как преносимость. Т.е. пока мы живём на компиляторе, в котором этот режим есть, у нас программа более-меднее производительно работает. Как только перешли на другую платформу, где этого режима нет, получили дерьмовый код.
Это совсем не довод.
Вы же не сидите с утра до ночи на чемоданах, в ожидании "а вдруг уехать придется" ?
Надо будет переходить на другую платформу - будем принимать соответствующие меры.
"Всепогодный" код а-ля Boost, который должен работать везде и всегда, нужен лишь в особых
случаях, и это обычно оговаривается сразу. Сейчас я живу на Windows, здесь три наиболее
ярких компилятора - это Visual C++, Intel C++ и MinGW. Все они поддерживают IPO уже
не первый год, поэтому не вижу смысла искать здесь причины, чтобы его не использовать.

Цитата Сообщение от Evg Посмотреть сообщение
Пример можно? Имеется в виду пример того, как наступишь, а потом долго ищешь причину
Да пожалуйста - разные версии хидера и библиотечного файла. Или разные определения
одного и того же метода в h и cpp. Это не во всех ситуациях отслеживается компилятором, а
потом такую ошибку можно искать очень долго. При единообразном подходе (реализация или
только в cpp, или только в h) она менее вероятна.

Цитата Сообщение от Evg Посмотреть сообщение
"И то, и другое" - это что?
Я имел в виду, что писать код, заранее прикидывая степень его "заинлайненности" -
это преждевременная оптимизация. Как в случае IPO, так и в случае реализации в хидерах.
castaway
Эксперт С++
4837 / 2976 / 367
Регистрация: 10.11.2010
Сообщений: 11,008
Записей в блоге: 10
Завершенные тесты: 1
19.08.2013, 21:42     реализация класса в .h файле хорошо или плохо? #45

Не по теме:

Я конечно извиняюсь что влезаю в Ваш диалог, но мне, как двоечнику по школе очень интересно, почему англоязычное слово header все переводят (произносят) как хидер ?



Добавлено через 32 минуты

Не по теме:

Меня что, все в "игнор добавили"? Неужели я успел "насолить" всем...

Jupiter
19.08.2013, 21:50
  #46

Не по теме:

castaway, англ "e" произноситься как "и"

castaway
Эксперт С++
4837 / 2976 / 367
Регистрация: 10.11.2010
Сообщений: 11,008
Записей в блоге: 10
Завершенные тесты: 1
19.08.2013, 22:55     реализация класса в .h файле хорошо или плохо? #47

Не по теме:

Jupiter, на самом деле, нет. В данном слове английская буква 'e' произносится как русская 'э' ( http://ru.forvo.com/word/header/ ).



Добавлено через 11 минут

Не по теме:

Английское слово "head" произносится как "хэд". Английское слово "bread" произносится как "брэд" ..



Добавлено через 51 минуту

Не по теме:

А самое интересное заключается в том, что слово неправильно произносят в шоу "Уральские пельмени": http://www.youtube.com/watch?v=C8Bz9V3JpU0

Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
19.08.2013, 23:13     реализация класса в .h файле хорошо или плохо? #48
Цитата Сообщение от Убежденный Посмотреть сообщение
Скажите, как давно Вы последний раз работали с этим режимом ?
В районе 2000 года (наверное даже пораньше). Интеловским компилятором не пользовался. Имею примерное представление о том, как устроен режим в gcc. Нынешнее устройство режима gcc в общей концепции совпадает с тем, с чем я работал 10 лет назад. Исхожу из того, что интеловский компилятор ведёт себя примерно так же. Потому что в конечном итоге вся межмодульная компиляция сводится к тому, что N файлов объединяются в один на уровне промежуточного представления

Цитата Сообщение от Убежденный Посмотреть сообщение
Время сборки увеличивается, но далеко не в такой прогрессии, как Вы указали.
Например, беру свой текущий проект: примерно 500 cpp-файлов, сборка под x86/amd64.
Общее время сборки порядка 3 минут (на Core-i5 2500). При этом треть этого времени составляет
сборка драйверов и компонентов, не относящихся к C++ и IPO. Да, и я имел дело с header-only
библиотеками, при подключении которых время сборки увеличивалось чуть ли не на порядок.
Не давая, по сути, ничего взамен. Ну кроме простоты сборки, разумеется, это вне вопросов
Надеюсь, что правильно понял описанное. Что из себя представляют эти 500 файлов в плане inline. Т.е. насколько глубоко построено дерево вызовов между модулями? Эффект по производительности от межмодульного режима в рамках проекта (без библиотек) по отношению к модульному замерялся? Я почему спрашиваю - коль скоро ты непосредственно с этим работаешь, то тебе не составит большого труда сделать замер. 3 минуты компиляции меня сильно смущают. Либо там файлы короткие, либо инлайн не глубокий (и, соответственно, не сильно эффективный).

Цитата Сообщение от Убежденный Посмотреть сообщение
Доказательства "плохости" есть ? Если нет - значит, ни о чем
Опять-таки на руках нет. Когда с этим работал непосредственно - было их хоть отбавляй. На коммерческих компиляторах от intel'а и sun microsystems. gcc в те времена ещё слабоват по производительности был, а потому его производительность не мерили. Методика была простая: брались стандартные тестовые пакеты spec92 и spec95, мерили на них производительность на ref-данных. Потом брались реальные задачи, которые работают в пакетном режиме и для которых сравнительно просто померить производительность. Это были sun'овские программы типа архиваторов, компиляторов и всякой подобной лабуды, которые свободно не распространялись, а следовательно, для компилятора это были условно неизвестные задачи. Практически по всем задачам производительность существенно отличалась от того, что было замерено на spec'ах. По тем временам в spec'ах разрешалось подавать дофига произвольных опций, а потому все компиляции spec'ов проводились в специально подобранных пиковых режимах, которые годами вылизывались у разработчиков компиляторов. Как только мы переключаемся на реальную задачу и базовые режимы (без всяких тонких настроек, которые нужно очень долго вылизывать), производительность сразу же резко начинала отличаться.

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

Цитата Сообщение от Убежденный Посмотреть сообщение
А знаете, в чем прелесть IPO ? В том, что для его использования не нужно прикладывать
никаких усилий. Он просто включен по умолчанию. Думаю, большинство пользователей, особенно
начинающих, вообще не знают, что их компилятор использует IPO. И тем не менее, получают
все выгоды от него
Чтобы не завязнуть в терминологии, я просто называю этот режим межмодульным. Термином IPO обычно называют межпроцедурные оптимизации (inter-procedural optimisations), ты его упомянул в названии режима (ссылка там не открылась), поэтому я подумал, что таким термином они назвали именно режим с межмодульными оптимизациями. Помодульный IPO нас не интересует, только межмодульный

Цитата Сообщение от Убежденный Посмотреть сообщение
Время компиляции увеличивается незначительно
Вот это и пугает больше всего. То бишь непонятно тогда, за счёт чего должен быть прирост по производительности, если компилятор на анализ и оптимизации не тратит время. А межпроцедурные оптимизации они всё-таки дорогие.

Цитата Сообщение от Убежденный Посмотреть сообщение
Ну а неудобство... В чем оно ?
В том, что модификация одного файла *.cpp (т.е. не хидера) вызывает перекомпиляцию всего проекта. Заявленные тобой 3 минуты на проект из 500 файлов, как я уже говорил, меня смущают. Когда руки доберутся, проверю всё-таки на gcc

Цитата Сообщение от Убежденный Посмотреть сообщение
Ну так может пора ему уже на свалку, этому инлайнингу, раз он не играет существенной роли в оптимизации ?
Ну дак если есть счётная задача на Си++, написанная при помощи stl-контейнеров с использованием пользовательских классов (типа список из экземпляров класса MyClass), то взять и скомпилировать с включенным и выключенным инлайном. Разница будет

Цитата Сообщение от Убежденный Посмотреть сообщение
Это совсем не довод
Для тебя, сидящего на одной архитектуре, не довод. Для тех, кто сразу де пишет программы под несколько платформ - довод

Цитата Сообщение от Убежденный Посмотреть сообщение
Да пожалуйста - разные версии хидера и библиотечного файла. Или разные определения одного и того же метода в h и cpp. Это не во всех ситуациях отслеживается компилятором, а потом такую ошибку можно искать очень долго. При единообразном подходе (реализация или только в cpp, или только в h) она менее вероятна
Эта проблема слишком искусственная. Возникает только в том случае если *.cpp не подключает соотвествующий *.h. Другими словами, нужно иметь очень кривые руки. Проблема того же порядка, что использование указателей. Тот, кто умеет ими пользоваться - будет пользоваться и радоваться жизни, кто не умеет - будет огребать геморрой с утра до ночи

Цитата Сообщение от Убежденный Посмотреть сообщение
Я имел в виду, что писать код, заранее прикидывая степень его "заинлайненности" -
это преждевременная оптимизация
А не надо прикидывать. Нужно просто взять за правило, что всё короткое (Set, Get, перегруженные операторы, конструкторы-деструкторы) нужно делать инлайном и выносить в хидера. А понадобитя оно, или нет - это забота компилятора. Понятно, что есть ряд случаев, когда конструкторы или операторы оказываются нетривиальными и большими и незачем их вытаскивать в хидера

Цитата Сообщение от Убежденный Посмотреть сообщение
Все они поддерживают IPO уже не первый год, поэтому не вижу смысла искать здесь причины, чтобы его не использовать
Для меня самая главная причина, почему не нужно использовать межмодульные оптимизации - это слишком долгая пересборка. После озвученных тобой цифр всё-таки попробую поэкспериментировать на gcc. В твой результат мне не верится. Точнее, верится, но только в том случае, когда межмодульный режим не даёт прироста производительности
castaway
19.08.2013, 23:22
  #49

Не по теме:

Evg, Убежденный, почему бы вам не подружиться?! Мне кажется, из вас вышла бы хорошая команда (заметьте, я специально не поставил "кавычки").

Убежденный
19.08.2013, 23:34
  #50

Не по теме:


Цитата Сообщение от castaway Посмотреть сообщение
Evg, Убежденный, почему бы вам не подружиться?!
Так мы же не ссоримся

Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
19.08.2013, 23:44     реализация класса в .h файле хорошо или плохо? #51
У меня даже появилось соображение, почему тот самый межмодульный режим, которым я пользовался давно, был такой медленный. И даже начинаю верить в твои 3 минуты на 500 файлов. Но пока всё равно подозреваю, что межмодульный IPO (в которые входит в том числе и инлайн) будет неглубоким и неагресссивным, но тем не менее небольшой прирост производительности за счёт небольшого увеличения времени компиляции - это однозначно лучше, чем ничего
castaway
19.08.2013, 23:44
  #52

Не по теме:

Цитата Сообщение от Убежденный Посмотреть сообщение
Так мы же не ссоримся
Ну кто бы спорил. Вы не ссоритесь, но команда из вас, вышла бы не плохая...

Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
20.08.2013, 10:14     реализация класса в .h файле хорошо или плохо? #53
Цитата Сообщение от Убежденный Посмотреть сообщение
Ну так может пора ему уже на свалку, этому инлайнингу, раз он не играет существенной роли в оптимизации?
За тестам далеко ходить не стал, взял примитивный тест на std::vector

C++
#include <vector>
 
/* Навесим сюда атрибут noinline, чтобы в обоих последующих
 * запусках внутренности функции были одними и теми же,
 * и не зависели от того, как у нас компилятор накрутит цикл
 * в точке вызова данной функции */
template <typename T>
void __attribute__((noinline))
test_copy (const T &lhs, T &rhs)
{
  rhs.clear();
  std::insert_iterator<T> ins_it(rhs, rhs.begin());
  std::copy(lhs.begin(), lhs.end(), ins_it);
}
 
int
main (void)
{
  std::vector<int> lhs, rhs;
  lhs.resize (100000);
  for (int i = 0; i < 10000; i++)
    test_copy (lhs, rhs);
}
Код
$ g++ t.cc -O3
$ time ./a.out
real    0m2.617s
user    0m2.549s
sys     0m0.002s

$ g++ t.cc -O3 -fno-inline
$ time ./a.out
real    1m21.080s
user    1m19.243s
sys     0m0.009s
Первый запуск соответствует "правильной" реализации классов, о которой я писал в посте #31. То есть в заголовочных файлах stl помещается именно реализация методов класса. И сделано оно так для того, чтобы эффективно отработать в совокупности всех используемых в программе контейнеров, шаблонов, пользовательских классов.

Второй запуск с опцией -fno-inlline (который запрещает компилятору делать инлайн) соответствует "академической" реализации классов, в которых интерфейс строго отделён от реализации (т.е. в хидер выносятся только описания, а все реализации остаются в файле *.cpp).

Грамотная реализация (в которой учтено, что компилятор работает с инлайном), как мы видим, отработала быстрее более, чем на порядок. Понятно, что пример искусственный, но он демонстрирует ту концепцию, в рамках которой разрабатывался язык Си++ со своим библиотечным окружением
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
20.08.2013, 11:36     реализация класса в .h файле хорошо или плохо? #54
Цитата Сообщение от Убежденный
Ну так может пора ему уже на свалку, этому инлайнингу, раз он не играет существенной роли в оптимизации?
Цитата Сообщение от Evg Посмотреть сообщение
Грамотная реализация (в которой учтено, что компилятор работает с инлайном), как мы видим, отработала быстрее более, чем на порядок.
Боюсь, Вы меня неправильно поняли. Я не отрицал полезность встраивания самого по себе.
Имелось в виду, что для современных компиляторов одинаково легко встроить как функцию,
определение которой "под рукой", так и функцию, определение которой находится в другой
единице трансляции/компоновки. То есть, возвращаясь к вопросу о "нанокомпиляторах".

Выполнил Ваш тест три раза (VC++2008, x64, Core i5-2500).
Единственное отличие от оригинального кода: test_copy сделана нешаблонной.

1) main и test_copy размещены в одной единице трансляции.
Результат: 3 минуты 57 секунд.

2) main и test_copy размещены в разных единицах трансляции.
Результат: 3 минуты 54 секунды.

3) main и test_copy размещены в разных единицах трансляции + использована опция LTCG.
Результат: 3 минуты 40 секунд.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
20.08.2013, 12:04     реализация класса в .h файле хорошо или плохо? #55
Цитата Сообщение от Убежденный Посмотреть сообщение
2) main и test_copy размещены в разных единицах трансляции.
Ты смысл теста неправильно понял. В комментарии перед test_copy написал, почему там атрибут noinline. Размещены они в одном модуле, или не в одном - большой роли не играет. Разница между твоими запусками 1-2 и 3 в том, что было проинлайнена функция test_copy. Но тест ведь вовсе НЕ на инлайн test_copy. Тест на то, что проинлайнятся конструкторы, методы и операторы классе vector, тела которых написаны в инклюдах. Другими словами, мы имеем одномодульный тест, к которому понятие межмодульных оптимизаций (LTCG) не имеет смысла. И вся производительность, вытянутая на этом тесте компилятором за счёт инлайна - она проявляется в одномодульном режиме компиляции за счёт "правильного" написания хидера.

Добавлено через 19 минут
Цитата Сообщение от Evg Посмотреть сообщение
У меня даже появилось соображение, почему тот самый межмодульный режим, которым я пользовался давно, был такой медленный. И даже начинаю верить в твои 3 минуты на 500 файлов
Соображение было следующее. То, с чем я работал раньше, сохраняло в качестве промежуточного представления самую раннюю стадию (т.е. представление высокого уровня очень близко к исходному тексту). Отсутствие замедление компиляции на интеловском компиляторе навело на мысль, что они сохраняют одну из последних стадий представления (т.е. сильно оптимизированное представление, близкое к низкому уровню). На таком представлении уже технически более сложно проводить инлайн и последующие за этим оптимизации (а именно ради последующих оптимизаций и делается инлайн, который сам по себе большого смысла не несёт). А потому последующий инлайн будет работать не сильно эффективно (по сравнению с технологией, которую я видел раньше). Отсюда и такой разброс во времени компиляции.

Попробовал режим -flto на gcc. Предположение по косвенным признакам подтверждается. Попробовал на нашем проекте (порядка 1500 исходников, объём исходников порядка 100 мегабайт). Время компиляции замедлилось несущественно (что-то типа 10%). Производительность не изменилась никак. Возможно, были какие-то ускорения в размерах флуктуаций измерений, но реального увеличения производительности не наблюл.
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
20.08.2013, 12:56     реализация класса в .h файле хорошо или плохо? #56
Цитата Сообщение от Evg Посмотреть сообщение
Ты смысл теста неправильно понял. В комментарии перед test_copy написал, почему там атрибут noinline. Размещены они в одном модуле, или не в одном - большой роли не играет. Разница между твоими запусками 1-2 и 3 в том, что было проинлайнена функция test_copy.
На самом деле она не была проинлайнена ни в одном из случаев.
Компилятор счел, что для этого нет оснований. Тем не менее, определенная оптимизация все
равно была выполнена. Кстати, на 32-битной машине я с помощью LTCG на этом же тесте получил
еще больший прирост - порядка 25%.

Цитата Сообщение от Evg Посмотреть сообщение
Но тест ведь вовсе НЕ на инлайн test_copy. Тест на то, что проинлайнятся конструкторы, методы и операторы классе vector, тела которых написаны в инклюдах. Другими словами, мы имеем одномодульный тест, к которому понятие межмодульных оптимизаций (LTCG) не имеет смысла. И вся производительность, вытянутая на этом тесте компилятором за счёт инлайна - она проявляется в одномодульном режиме компиляции за счёт "правильного" написания хидера.
В этом тесте отсутствует главное - доказательство того, что реализация в заголовках
приводит к генерации более быстрого кода, чем реализация в исходных файлах.
И это хитро, потому что STL, как и надлежит шаблонным классам, реализована в
заголовках, и подтвердить или опровергнуть обратное, используя код STL, не
представляется возможным.

Думаю, единственная возможность расставить все точки над "i" - это найти какую-нибудь
библиотеку, которая может собираться как в варианте header-only, так и как набор h+cpp.
Возможно, в Boost что-то такое найдется. И вот по результатам ее запуска уже делать выводы.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
20.08.2013, 13:08     реализация класса в .h файле хорошо или плохо? #57
Цитата Сообщение от Убежденный Посмотреть сообщение
Кстати, на 32-битной машине я с помощью LTCG на этом же тесте получил еще больший прирост - порядка 25%
Без конкретного анализа конкретного бинарника тут сказать что-то сложно. На ум приходят две вещи, благодаря которым в конкретно данном тесте есть профит от LTGC:
- в этом режиме более агрессивно работает инлайн (или какие-то дополнительные оптимизации)
- библиотеки stl поставляются с сохранённым промежуточным представлением.
Других вариантов, почему межмодульные оптимизации имеют эффект на исходнике из одного модуля, я не вижу.

Цитата Сообщение от Убежденный Посмотреть сообщение
В этом тесте отсутствует главное - доказательство того, что реализация в заголовках приводит к генерации более быстрого кода
Я же всё написал. В gcc подаём опцию -fno-inline, после чего мы начинаем работать так же, как если бы методы были реализованы в другом файле. То, что они реализованы и скомпилированы в этом файле, принципиально не отличается, как если бы их код подцепился из другого файла (потому что не один ли фиг из какого места подцепится один и тот же код при линковке).

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

Цитата Сообщение от Убежденный Посмотреть сообщение
И это хитро, потому что STL, как и надлежит шаблонным классам, реализована в
заголовках, и подтвердить или опровергнуть обратное, используя код STL, не
представляется возможным
Я уже писал, что любой пользовательский класс почти наверняка будет использован в совокупности с контейнерами или ещё какими шаблонами из STL. И именно в этом месте инлайн даст огромное преимущество. Преимущество будет в том числе и без работы с STL, но чем меньше размер паровоза из всяких шаблонов, тем меньше будет разница между наличием и отсутствием инлайна. Примитивный пример напишу чуть позже
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
20.08.2013, 13:37     реализация класса в .h файле хорошо или плохо? #58
Цитата Сообщение от Evg Посмотреть сообщение
Без конкретного анализа конкретного бинарника тут сказать что-то сложно.
Я смотрел ассемблерный код. Он просто разный в некоторых местах.
Думаю, объяснить эффект LTCG можно, если думать про него не как про IPO, а именно
как про вторую фазу оптимизации. Которая в обычном режиме не выполняется.

Цитата Сообщение от Evg Посмотреть сообщение
В gcc подаём опцию -fno-inline, после чего мы начинаем работать так же, как если бы методы были реализованы в другом файле.
Не могу с этим согласиться.
Эффект от -fno-inline более глубокий, чем ты описываешь - он отключает инлайнинг по всему коду.
Если у меня есть функции A и B, размещенные в разных единицах трансляции, то даже без IPO я
могу рассчитывать хотя бы на то, что компилятор встроит код STL в тела A и B.
При использовании -fno-inline и этого встраивания не будет.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16821 / 5242 / 318
Регистрация: 30.03.2009
Сообщений: 14,118
Записей в блоге: 26
20.08.2013, 14:06     реализация класса в .h файле хорошо или плохо? #59
Цитата Сообщение от Убежденный Посмотреть сообщение
Эффект от -fno-inline более глубокий, чем ты описываешь - он отключает инлайнинг по всему коду
Да, отключает. Но очень много всего в нашем случае некритично, ибо будет составлять проценты, может десятки процентов. В нашем случае речь шла о разнице более, чем на порядок, а потому всеми этими процентами и десятками процентов можно пренебречь. Во всяком случае для меня это очевидно (т.к. есть опыт в этой области). Но это вовсе не означает, что это так же очевидно тебе и другим. Поэтому привожу обещанный ранее пример. В примере у нас имеются элементы Elem и массив Elem'ов. Создаём массив, инициализируем его, затем инкрементируем значения

========== Реализация методов в хидере ==========

C++
// Файл t1.cc
 
#include "t.h"
 
#define SIZE 10000
#define COUNT 100000
 
/* Атрибут втыкаем для того, чтобы отрезаться от цикловых оптимизаций
 * вточке вызова и замерить условно чистое время работы данной функции */
void __attribute__((noinline))
test_func (void)
{
  Array array (SIZE);
 
  for (int i = 0; i < SIZE; i++)
    {
      Elem elem (i);
      array.Set (i, elem);
    }
 
  for (int i = 0; i < SIZE; i++)
    {
      Elem elem = array.Get (i);
      elem.Set (elem.Get() + 1);
      array.Set (i, elem);
    }
}
 
int
main (void)
{
  for (int i = 0; i < COUNT; i++)
    test_func ();
 
  return 0;
}
C++
// Файл t.h
 
class Elem
{
private:
  int x;
public:
  Elem () { x = 0; }
  Elem (int _x) { x = _x; }
  int Get (void) { return x; }
  void Set (int _x) { x = _x; }
};
 
class Array
{
private:
  Elem *elems;
public:
  Array (unsigned size) { elems = new Elem[size]; }
  ~Array () { delete elems; }
  Elem Get (unsigned index) { return elems[index]; }
  void Set (unsigned index, const Elem &elem) { elems[index] = elem; }
};
Код
$ g++ t1.cc -O3
$ time ./a.out
real    0m1.719s
user    0m1.718s
sys     0m0.001s
========== Реализация методов в отдельном файле ==========

Файл t1.cc остаётся без изменений.

Из файла t.h удаляем все реализации

C++
// Файл t.h
 
class Elem
{
private:
  int x;
public:
  Elem ();
  Elem (int _x);
  int Get (void);
  void Set (int _x);
};
 
class Array
{
private:
  Elem *elems;
public:
  Array (unsigned size);
  ~Array ();
  Elem Get (unsigned index);
  void Set (unsigned index, const Elem &elem);
};
Добавляем файл t2.cc с реализацией методов

C++
// Файл t2.cc
 
#include "t.h"
 
Elem::Elem ()
{
  x = 0;
}
 
Elem::Elem (int _x)
{
  x = _x;
}
 
int
Elem::Get (void)
{
  return x;
}
 
void
Elem::Set (int _x)
{
  x = _x;
}
 
Array::Array (unsigned size)
{
  elems = new Elem[size];
}
 
Array::~Array ()
{
  delete elems;
}
 
Elem
Array::Get (unsigned index)
{
  return elems[index];
}
 
void
Array::Set (unsigned index, const Elem &elem)
{
  elems[index] = elem;
}
Код
$ g++ t1.cc t2.cc -O3
$ time ./a.out
real    0m24.781s
user    0m24.529s
sys     0m0.172s
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
20.08.2013, 14:55     реализация класса в .h файле хорошо или плохо?
Еще ссылки по теме:

C++ Переменные на русском языке - хорошо или плохо?
Реализация шаблонов класса в инлайн файле C++
C++ Реализация шаблонов класса в инлайн файле

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Убежденный
Системный программист
 Аватар для Убежденный
14173 / 6188 / 981
Регистрация: 02.05.2013
Сообщений: 10,295
Завершенные тесты: 1
20.08.2013, 14:55     реализация класса в .h файле хорошо или плохо? #60
+1, спасибо за реальный тест.

Запустил его на своей рабочей машине, код не трогал, только поменял декларацию
"__attribute__((noinline))" на "__declspec(noinline)" (в VC++ это одно и то же).
Также, для более рельефных результатов, я увеличил оба значения SIZE и COUNT в пять раз.

Итак, результаты (x86, Visual C++ 2008, Core i5-2500).

1) Реализация методов в хидере.

Время: 1 минута 3 секунды.

2) Реализация методов в отдельном файле.

И здесь нас ждал вполне предсказуемый фейл.
Время: 4 минуты 9 секунд.

3) Реализация методов в отдельном файле + опция LTCG.

Время: 25 секунд.

Кстати, в точности такой же результат получается, если использовать LTCG для
реализации методов в хидере. Подтверждая, что компилятору без разницы, где находится
определение - в заголовке или в файле реализации.
Yandex
Объявления
20.08.2013, 14:55     реализация класса в .h файле хорошо или плохо?
Ответ Создать тему
Опции темы

Текущее время: 07:52. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru