Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются
#include myfile1.cpp"
#include myfile2.cpp"
и так далее, далее gcc main.cpp компилируется за 1 вызов.
Может быть можно это сделать без создания временного файла main.cpp ?
Сравнение с традиционным способом компиляции (когда каждый .cpp в отдельный .o файл)
Недостатки:
— время сборки при изменении 1 файла, такое же как при сборки целиком проекта.
— #define одного cpp могут повлять на другой
— конфликт имён может быть, если имена глобальных функций или переменных совпадут
— разные опции компиляции приходиться задавать через #pragma
— gcc не все неиспользуемые функции выкидывает. ( НЕ разобрался пока ещё почему так).
Преимущество:
— время сборки целиком проекта значительно быстрее. Например препроцессор добавляет 2Мб в каждому файлу где есть #include <windows.h>
— не нужно заботиться о указании noexcept так как когда все функции в одном объектном файле, компилятор сам может правильно определить данный атрибут функции.
— можно писать в удобном стиле, когда деларация и имплементация сразу. Есть какой-то термин для данного стиля ?
Деларации и имплементация сразу. Можно конечно поробовать в таком стиле традиционным способом скомпилировать, но тогда будет много имплементаций одного класса (в каждом .o файле, где он используется). И могут быть проблемы: по разному реализовано может быть (в зависимости от макросов и опций компилятора), неопределённость при линковке — неизвестно какую реализацию возмёт линковщик. Поэтому я считаю, что опастно так делать при традиционном способе компиляции.
Может я что-то упустил ? Покритикуйте плиз аргументированно.
Здравствуйте, maks1180, Вы писали:
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
— Быстрая инкрементальная компиляция критически важна для скорости разработки
— Предложенный вариант далеко не факт, что быстрее — нужно собрать такой мегацпп и проверить
scf>- Быстрая инкрементальная компиляция критически важна для скорости разработки scf>- Предложенный вариант далеко не факт, что быстрее — нужно собрать такой мегацпп и проверить
Да, я указал "время сборки при изменении 1 файла, такое же как при сборки целиком проекта.".
У меня в проекте 37 cpp файлов.Целиком собирается:
45 секунд традиционным способом ( в среднем 1-2 секунды на 1 cpp файл + 1-2 секунды на линковку)
8 секунд способом через main.cpp файл.
8 секунд мне кажется вполне нормально для разработки. Можно попробывать ещё прехеадеры выделить и разнести их в другой cpp файл, тогда будет примерно 4 секунды время компиляции всего проекта.
Здравствуйте, maks1180, Вы писали:
M>Деларации и имплементация сразу. Можно конечно поробовать в таком стиле традиционным способом скомпилировать, но тогда будет много имплементаций одного класса (в каждом .o файле, где он используется). И могут быть проблемы: по разному реализовано может быть (в зависимости от макросов и опций компилятора), неопределённость при линковке — неизвестно какую реализацию возмёт линковщик. Поэтому я считаю, что опастно так делать при традиционном способе компиляции.
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
Очевидно такой способ компиляции медленнее начиная с какого-то относительно небольшого проекта.
Тот же единый файл sqlite компилируется секунд 30 в режиме оптимизации, а компиляция одного из сотни файлов
sqlite и линковка займет максимум пару секунд. А проблему с повторным парсингом одних и тех же больших системных
заголовочных файлов нормально решает использование "precompiled headers", а скоро и модули можно будет использовать.
Z>При сборке sqlite такой термин называется https://www.sqlite.org/amalgamation.html, Z>в cmake это называется https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html .
Спасибо, буду знать.
Z>Очевидно такой способ компиляции медленнее начиная с какого-то относительно небольшого проекта. Z>Тот же единый файл sqlite компилируется секунд 30 в режиме оптимизации, а компиляция одного из сотни файлов Z>sqlite и линковка займет максимум пару секунд. А проблему с повторным парсингом одних и тех же больших системных Z>заголовочных файлов нормально решает использование "precompiled headers", а скоро и модули можно будет использовать.
Можно для разработки выносить изменяемые cpp файлы в отдельную компиляцию.
Я это затеял ради:
— не нужно заботиться о указании noexcept
— можно писать в удобном стиле, когда деларация и имплементация сразу
Что по этим пунктам можете сказать, прав я или нет ?
Здравствуйте, maks1180, Вы писали:
M>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>#include myfile1.cpp" M>#include myfile2.cpp" M>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>Может быть можно это сделать без создания временного файла main.cpp ?
Нормальный способ это когда каждый файл .cpp компилируется в объектниек и складывается в библиотеку, которые потом подключаются
при этом все зависимость указываются в makefile
M>Недостатки: M>- #define одного cpp могут повлять на другой
Это самое весёлое. Оно может собраться но работать не так как задумано.
M>Преимущество: M>- время сборки целиком проекта значительно быстрее. Например препроцессор добавляет 2Мб в каждому файлу где есть #include <windows.h>
Что вам мешает делать функциональные модули не подключающие windows.h и другой мусор в заголовочных файлах?
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
В общем случае такой путь тупиковый.
Здравствуйте, maks1180, Вы писали:
M>>>Может я что-то упустил ? Покритикуйте плиз аргументированно. _>>В общем случае такой путь тупиковый. M>Почему ? Где аргументы ?
1. нельзя собирать частями
2. не распараллелить
3. не масштабируется
4. не собрать сторонние библиотеки
5. вносить исправления замудохаешся
6. возникает куча не нужных проблем
7. вместо ускорения можно получить замедление всего процесса
Здравствуйте, maks1180, Вы писали:
M>Можно для разработки выносить изменяемые cpp файлы в отдельную компиляцию. M>Я это затеял ради: M>— не нужно заботиться о указании noexcept
Почему не нужно? Возможно компилятор во время оптимизации сумеет
их правильно рассчитать, но точно также он может это сделать и во время LTO (link-time-optimization).
Но вообще есть куча оптимизаций, которые делаются во время LTO,
девертулизация вызовов, встраивание вызовов, удаление дубликатов, неиспользуемых функций
и т.д. и т.п.
И при этом нужно заметить, что "noexcept" нужен не только компилятору,
но и разработчику, чтобы понять являются ли исключения частью "контракта" или нет.
M>— можно писать в удобном стиле, когда деларация и имплементация сразу
Ну так можно писать и в случае разбития на несколько файлов.
Реализованные прямо в объявлении класса члены-функции по умолчанию считаются
"inline" и можно cделать "#include" без проблем в несколько единиц трансляции без проблем,
накаких ошибок линковки не будет и все будет работать правильно.
Правда субъективно такой код читать сложнее, нельзя охватить разом интерфейс который предоставляет класс,
так как интерфейс прячется за реализацией.
Z>Ну так можно писать и в случае разбития на несколько файлов. Z>Реализованные прямо в объявлении класса члены-функции по умолчанию считаются Z>"inline" и можно cделать "#include" без проблем в несколько единиц трансляции без проблем, Z>накаких ошибок линковки не будет и все будет работать правильно.
Я описал в топике "будет много имплементаций одного класса (в каждом .o файле, где он используется). И могут быть проблемы: по разному реализовано может быть (в зависимости от макросов и опций компилятора), неопределённость при линковке — неизвестно какую реализацию возмёт линковщик. Поэтому я считаю, что опастно так делать при традиционном способе компиляции"
Z>Правда субъективно такой код читать сложнее, нельзя охватить разом интерфейс который предоставляет класс, так как интерфейс прячется за реализацией.
Это да, иногда труднее, но иногда проще. Если нужно видеть реализацию, смотришь в cpp файл и не понимаешь static или нет функция, а так же не видишь параметры по умолчанию. Приходиться бегать по двум файлам.
Зато править такой код намного проще, поэтому все современные языки в таком стиле — Java, C#, php и т.д.
А посмотреть интерфейс удобно через IDE.
M>>— не нужно заботиться о указании noexcept
Z>Почему не нужно?
Потому что компилятор видит все функции, и сам правильно расчитает.
Z>Возможно компилятор во время оптимизации сумеет их правильно рассчитать, но точно также он может это сделать и во время LTO (link-time-optimization).
Расчитать может и сможет, он уже файлы откомпилированы из предположение — что все внешние (относительно .o файла) фукнции, которые не объявлены noexcept, могут генерировать исключение.
Поэтому код будет больше и медленее, чем когда компилируются сразу все функции
Здравствуйте, maks1180, Вы писали:
M>Недостатки: M>- конфликт имён может быть, если имена глобальных функций или переменных совпадут
Не может быть конфликта имён, если имена глобальных функций или переменных совпадут. Это как это?
Конфликт имён будет, если имена файл-локальных (внутреннее связывание) функций или переменных совпадут.
M>- можно писать в удобном стиле, когда деларация и имплементация сразу. Есть какой-то термин для данного стиля ?
Ничего нового в этом нет. Файл-локальные функции обычно именно так и имплементируются. Так же имплементируются инлайновые функции и [могут имплементироваться] шаблоны функций.
Отдельная декларация (в хедере) и отдельная имплементация делаются только для внешних функций единицы трансляции.
M>Деларации и имплементация сразу. Можно конечно поробовать в таком стиле традиционным способом скомпилировать, но тогда будет много имплементаций одного класса (в каждом .o файле, где он
используется).
"много имплементаций одного класса (в каждом .o файле, где он используется)" Это как это? О чем речь?
Здравствуйте, maks1180, Вы писали:
M>Я описал в топике "будет много имплементаций одного класса (в каждом .o файле, где он используется). И могут быть проблемы: по разному реализовано может быть (в зависимости от макросов и опций компилятора), неопределённость при линковке — неизвестно какую реализацию возмёт линковщик. Поэтому я считаю, что опастно так делать при традиционном способе компиляции"
Так в "unity build" та же опасность, если в одном из .cpp файлов есть __USE_FILE_OFFSET64=1,
а в другом __USE_FILE_OFFSET64=0, то при объединении эти файлов в один будут те же самые проблемы или хуже,
так как потенциально сломается не только пара файлов, где макрос по другому определен,
а могут сломаться все файлы ниже нашего одного огромного исходника.
.
M>Зато править такой код намного проще, поэтому все современные языки в таком стиле — Java, C#, php и т.д. M>А посмотреть интерфейс удобно через IDE.
Ну если у нас есть IDE, то собственно никакой разницы. IDE и тебя в одно нажатие между
файлом с интерфейсом и реализацией перекинет, и удалит метод и там и там и так далее.
Здравствуйте, maks1180, Вы писали:
M>>>— не нужно заботиться о указании noexcept
Z>>Почему не нужно? M>Потому что компилятор видит все функции, и сам правильно расчитает.
При LTO он сделает тоже самое,
M>Расчитать может и сможет, он уже файлы откомпилированы из предположение — что все внешние (относительно .o файла) фукнции, которые не объявлены noexcept, могут генерировать исключение.
LTO не так работает, по крайней мере в gcc/clang.
В объектом файле лежит не только код, но и дополнительная мета информация.
Посмотрите любое "HOWTO" по использованию "lto", там с особым флагом нужно
откомпилировать каждую единицу трансляции, а не только линковщик вызвать с особым флагом.
Поэтому потенциально все оптимизации доступные с помощью объединения всех исходников в один огромный
файл доступны и при LTO. Как например ту же девиртулизацию делать,
если у тебя уже есть ассебмлерный код с парой инструкций, где загружается адрес функции из таблицы вирутальных функций
и вызывается, LTO на таком уровне было бы огромной архитектурной ошибкой.
Здравствуйте, maks1180, Вы писали:
M>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>#include myfile1.cpp" M>#include myfile2.cpp" M>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>Может быть можно это сделать без создания временного файла main.cpp ?
Фактически это отказ от использования принципа раздельной компиляции. То есть, на выходе препроцессора каждый раз будет образовываться мегамонстр, который будет постоянно расти во времени и в один прекрасный момент просто не поместится в оперативной памяти. О каком ускорении компиляции может идти речь при этом?
Другой момент: все анонимные пространства имен сливаются в одно большое. С практической точки зрения это равносильно отказу от использования локальных имен.
И самое главное, пожалуй: такой подход способстует образованию неоправданных паразитных зависимостей в коде, объемы которых будут расти как снежный ком. В долгосрочной перспективе любой проект будет стремиться к такому состоянию, когда в нем невозможно будет сделать даже относительно безобидные изменения, не поломав какую-нибудь функциональность.
P.S. А что с юнит-тестами? Где будут находиться юнит-тесты? Если в одном котле с продакшн-кодом, то все проблемы еще больше усугубляются. Если отдельно, тогда снова вернулись к раздельной компиляции и использованию заголовочных файлов.
P.P.S. Еще предвижу веселуху с порядком включения. Рано или позно окажется, что в модуле А нужно исбользовать что-то из модуля B, но поменять их местами нельзя, потому что модуль B зависит по порядку включения от модуля C. Конечно же, очевидным решением будет использование предварительного объявления. А когда количество одинаковых предварительных объявлений начнет зашкаливать за пределы здравого смысла будет принято решение о создании специальных файлов, в которые разработчики обязаны будут выносить все объявления. Закончится все адом.
M>>Зато править такой код намного проще, поэтому все современные языки в таком стиле — Java, C#, php и т.д. M>>А посмотреть интерфейс удобно через IDE.
Z>Ну если у нас есть IDE, то собственно никакой разницы. IDE и тебя в одно нажатие между Z>файлом с интерфейсом и реализацией перекинет, и удалит метод и там и там и так далее.
Ну так нужно нажимать и прыгать по файлам, гораздо удобнее когда всё что нужно сразу видно.
Назовите хоть один современный язык у которого такой же подход — раздельное декларация и реализация функций/классов ?
Мне кажется это рудимент, от которого нужно избавляться.
R>Фактически это отказ от использования принципа раздельной компиляции. То есть, на выходе препроцессора каждый раз будет образовываться мегамонстр, который будет постоянно расти во времени и в один прекрасный момент просто не поместится в оперативной памяти. О каком ускорении компиляции может идти речь при этом?
Да, принцип раздельной компиляции делает код менее оптимизированным.
Препроцессор для #include <windows.h> добавляет 2Мб, мой самый большой проект около 2,5мб кода. Поэтому вряд ли я упрусь, когда он не поместится в оперативной памяти.
А если упрусь разобью проект на 2 или 3 cpp — разделю файлы по малозависимым группам.
R>P.S. А что с юнит-тестами? Где будут находиться юнит-тесты? Если в одном котле с продакшн-кодом, то все проблемы еще больше усугубляются. Если отдельно, тогда снова вернулись к раздельной компиляции и использованию заголовочных файлов.
Юнит-тесты у меня в отдельном файле, раздельной компиляции не требует, так как включается после.
R>P.P.S. Еще предвижу веселуху с порядком включения. Рано или позно окажется, что в модуле А нужно исбользовать что-то из модуля B, но поменять их местами нельзя, потому что модуль B зависит по порядку включения от модуля C. Конечно же, очевидным решением будет использование предварительного объявления. А когда количество одинаковых предварительных объявлений начнет зашкаливать за пределы здравого смысла будет принято решение о создании специальных файлов, в которые разработчики обязаны будут выносить все объявления. Закончится все адом.
Да, это мне тоже не нравиться. Ещё есть мысль сделать свой парсер или препроцессор, который будет 1 файл, разбивать на два cpp+h, что-бы проблем с зависимостью не было.
Но тут важно это сделать так что-бы при ошибки компилятор выдавал название и строку из оригинального файла, а не из созданного парсером.
Я удивлён почему до сих пор такого нет, или может есть ?
Здравствуйте, maks1180, Вы писали:
M>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>#include myfile1.cpp" M>#include myfile2.cpp" M>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>Может быть можно это сделать без создания временного файла main.cpp ?
M>Сравнение с традиционным способом компиляции (когда каждый .cpp в отдельный .o файл)
Очень похоже что делает Unity build. Есть опыт использования этой тулзы, заключается в том, что т.к. compilation unit — один (для небольших проектов), то надо держать все side эффекты от этого.
1. Теоретические проблемы с дефайнами. В целом если такие проблемы возможны, я бы сказал, что это повод что-то переделать.
2. static функции, так они могут совпадать в разных файлах, а с такой амальгамацией — не могут, нужно следить за этим, но в целом это не проблема, т.к. будет просто ошибка компиляции.
Мне кажется, лучше попробовать разобраться с предкомпилированными заголовочными файлами.
Здравствуйте, maks1180, Вы писали:
M>Но тут важно это сделать так что-бы при ошибки компилятор выдавал название и строку из оригинального файла, а не из созданного парсером.
Это как раз не проблема, есть прагма, которая эту информацию устанавливает для сгенерированного файла.
vsb>Мне кажется, лучше попробовать разобраться с предкомпилированными заголовочными файлами.
Это да, но я перешёл не из-за скорости компиляции, а из-за
1) можно писать в удобном стиле, когда деларация и имплементация сразу.
2) не нужно заботиться о указании noexcept так как когда все функции в одном объектном файле, компилятор сам может правильно определить данный атрибут функции.
Да и вообще код получается более отпимизирован, если компилятор видит сразу все функции.
Здравствуйте, maks1180, Вы писали:
M>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>#include myfile1.cpp" M>#include myfile2.cpp" M>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>Может быть можно это сделать без создания временного файла main.cpp ?
gcc myfile1.cpp myfile2.cpp -Os -o program.exe ?
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
В большом проекте нужна инкрементальная сборка и пофайловая компиляция поэтому.
Здравствуйте, kov_serg, Вы писали:
_>Здравствуйте, maks1180, Вы писали:
M>>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>>#include myfile1.cpp" M>>#include myfile2.cpp" M>>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>>Может быть можно это сделать без создания временного файла main.cpp ?
_>Нормальный способ это когда каждый файл .cpp компилируется в объектниек и складывается в библиотеку, которые потом подключаются
Это опасная ловушка. Не нужно делать библиотеку, если вам не нужна осознанно именно библиотека.
Потому, что потом запросто можно наделать в разных объектниках дублирующиеся символы, и слинкуется какой попало.
Если конечно не линковать через -Wl,--whole-archive.
M>>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>>#include myfile1.cpp" M>>#include myfile2.cpp" M>>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>>Может быть можно это сделать без создания временного файла main.cpp ?
fk0> gcc myfile1.cpp myfile2.cpp -Os -o program.exe ?
Это будет — компилирования по отдельности cpp файлов и линковка
Здравствуйте, maks1180, Вы писали:
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
на конференции C++ Russia был интересный доклад по unity build. Его не нашёл, кину статью от PVS https://habr.com/ru/company/pvs-studio/blog/344534/
Можно только добавить к вашему подходу, что надо делать не один большой файл, а N больших (где N — количество ядер).
Ну и надо искать готовое решение. Самому устранять все неоднозначности макросов, анонимных namespaces будет нелегко.
Меня в самом подходе смущает только описание ошибок компиляции. Оно в С++ хромает (хорошее, но хромает). А после слияния будет вообще мрак. Ведь ссылка идёт на место в сгенерированном временном файле. И закрадывается главный вопрос: "стоит ли овчинка выделки?"
Здравствуйте, maks1180, Вы писали:
M>>>Может я что-то упустил ? Покритикуйте плиз аргументированно.
fk0>> В большом проекте нужна инкрементальная сборка и пофайловая компиляция поэтому.
M>На стадии разработки возможно, если так будет быстрее компилироваться. Но на стадии релиза нет, так как она создаёт менее оптимизированный код.
Для более оптимизированного кода есть LTO. Про анонимные неймспейсы кто-то уже в треде высказался -- включать всё через include ещё та глупость,
которая может породить трудно выявляемые баги.
Здравствуйте, maks1180, Вы писали:
M>>>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>>>#include myfile1.cpp" M>>>#include myfile2.cpp" M>>>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>>>Может быть можно это сделать без создания временного файла main.cpp ?
fk0>> gcc myfile1.cpp myfile2.cpp -Os -o program.exe ?
M>Это будет — компилирования по отдельности cpp файлов и линковка
Ну зато "без создания временного файла" (на самом деле компилятор где-то в /tmp что-то создаст наверное...)
Unreal Engine использует unity build по дефолту, чем они порядочно увеличили скорость компиляции своей эпичной кодобазы.
Там где то по 10ти исходников компиляются как один.
Т.е. подход в целом работает, но нужно искать какой то баланс самому либо тулзами. Такой подход ест достаточно много оперативки. И страдает инкрементальный билд, хотя линковка может ускориться — нужно мерять.
В общем неблагодарное это дело, я бы просто C++ модулей дождался.
Здравствуйте, maks1180, Вы писали:
M>Покритикуйте плиз такой метод компиляции, создаётся файл main.cpp в него добавляются M>#include myfile1.cpp" M>#include myfile2.cpp" M>и так далее, далее gcc main.cpp компилируется за 1 вызов. M>Может быть можно это сделать без создания временного файла main.cpp ?
Это называется "амальгамация". Некоторый софт так распространяется, у SQLite, например, есть амальгамированная версия
M>Преимущество: M>- время сборки целиком проекта значительно быстрее. Например препроцессор добавляет 2Мб в каждому файлу где есть #include <windows.h>
Препроцессор — это фигня, это довольно тупая текстовая обработки
M>- не нужно заботиться о указании noexcept так как когда все функции в одном объектном файле, компилятор сам может правильно определить данный атрибут функции. M>- можно писать в удобном стиле, когда деларация и имплементация сразу. Есть какой-то термин для данного стиля ?
Header-only библиотека? А что мешает писать в таком стиле без сваливания всего в одну кучу?
M>Может я что-то упустил ? Покритикуйте плиз аргументированно.
Это удобно для распространения библиотеки в сорцах, чтобы пользователю не возится с кучей сорцов, а подключать один файл. В процессе разработки — это куча граблей на ровном месте. Можно, но зачем?