Clang vs GCC - который производит лучшие двоичные файлы?
Я в настоящее время использую GCC, но я недавно обнаружил Clang, и я размышляю о переключении. Однако есть один решающий фактор - качество (скорость, объем памяти, надежность) двоичных файлов, которые он производит - если gcc -O3
может создать бинарный файл, который работает на 1% быстрее или занимает на 1% меньше памяти, это прерыватель сделки.
Clang может похвастаться лучшими скоростями компиляции и меньшим объемом памяти во время компиляции, чем GCC, но я действительно заинтересован в тестах / сравнениях итогового скомпилированного программного обеспечения - не могли бы вы указать на некоторые из них или описать свой опыт?
7 ответов
Вот несколько современных, хотя и узких, моих находок с GCC 4.7.2 и Clang 3.2 для C++.
ОБНОВЛЕНИЕ: сравнение GCC 4.8.1 v clang 3.3, приложенное ниже.
ОБНОВЛЕНИЕ: сравнение с GCC 4.8.2 v clang 3.4 добавлено к этому.
Я поддерживаю инструмент OSS, созданный для Linux с GCC и Clang, а также с компилятором Microsoft для Windows. Этот инструмент, coan, является препроцессором и анализатором исходных файлов C/C++ и таких строк кода: его профили вычислительных профилей для анализа рекурсивного спуска и обработки файлов. Ветвь разработки (к которой относятся эти результаты) в настоящее время содержит около 11K LOC в около 90 файлах. Теперь он закодирован в C++, который богат полиморфизмом и шаблонами, но во многих исправлениях его все еще пугает его не столь далекое прошлое во взломанной C. Семантика перемещения явно не используется. Это однопоточный. Я не прилагал серьезных усилий для его оптимизации, в то время как "архитектура" остается в значительной степени ToDo.
Я использовал Clang до 3.2 только в качестве экспериментального компилятора, потому что, несмотря на его превосходную скорость компиляции и диагностику, его стандартная поддержка C++11 отстала от современной версии GCC по отношению к coan. С 3.2 этот пробел был закрыт.
Моя система тестирования Linux для текущей разработки коанов обрабатывает примерно 70 тыс. Исходных файлов в виде комбинации из одного файла парсера, стресс-тестов, потребляющих тысячи файлов, и сценариев, тестирующих файлы < 1К. Наряду с отчетом о результатах теста, жгут собирает и отображает общее количество использованных файлов и время выполнения, использованное в coan
(он просто передает каждую командную строку coan в Linux time
команда и захватывает и складывает сообщенные номера). Сроки польщены тем фактом, что любое количество тестов, которые занимают 0 измеримого времени, все в сумме составят 0, но вклад таких тестов незначителен. Сроки статистики отображаются в конце make check
как это:
coan_test_timer: info: coan processed 70844 input_files.
coan_test_timer: info: run time in coan: 16.4 secs.
coan_test_timer: info: Average processing time per input file: 0.000231 secs.
Я сравнил производительность тестового жгута как между GCC 4.7.2 и Clang 3.2, при прочих равных условиях, за исключением компиляторов. Начиная с Clang 3.2, я больше не требую какой-либо препроцессорной дифференциации между участками кода, которые GCC будет компилировать, и альтернативами Clang. Я собрал одну и ту же библиотеку C++ (GCC) в каждом случае и провел все сравнения последовательно в одном терминальном сеансе.
Уровень оптимизации по умолчанию для моей сборки выпуска -O2. Я также успешно протестировал сборки на -O3. Я протестировал каждую конфигурацию 3 раза подряд и усреднил 3 результата со следующими результатами. Число в ячейке данных - это среднее число микросекунд, потребляемых исполняемым файлом coan для обработки каждого из входных файлов ~70K (чтение, анализ и запись и диагностика).
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 231 | 237 |0.97 |
----------|-----|-----|-----|
Clang-3.2 | 234 | 186 |1.25 |
----------|-----|-----|------
GCC/Clang |0.99 | 1.27|
Любое конкретное приложение, скорее всего, будет иметь черты, которые несправедливо влияют на сильные и слабые стороны компилятора. Строгий бенчмаркинг использует разнообразные приложения. Имея это в виду, заслуживающие внимания особенности этих данных:
- -O3 оптимизация была незначительно вредна для GCC
- -O3 оптимизация была важна для Clang
- При оптимизации -O2, GCC был быстрее, чем Clang, только на волосок
- При оптимизации -O3 Clang был значительно быстрее GCC.
Еще одно интересное сравнение двух компиляторов произошло случайно после этих результатов. Коан свободно использует умные указатели, и один из них активно используется при обработке файлов. Этот конкретный тип умного указателя был typedef'd в предыдущих выпусках ради дифференциации компилятора, чтобы быть std::unique_ptr<X>
если настроенный компилятор имел достаточно зрелую поддержку для его использования как такового, а в противном случае std::shared_ptr<X>
, Уклон к std::unique_ptr
было глупо, так как эти указатели были фактически переданы, но std::unique_ptr
выглядел как вариант слесаря для заменыstd::auto_ptr
в тот момент, когда варианты C++11 были для меня новыми.
В ходе экспериментальных сборок, чтобы измерить постоянную потребность Clang 3.2 в этой и аналогичной дифференциации, я случайно построил std::shared_ptr<X>
когда я намеревался построить std::unique_ptr<X>
и с удивлением заметил, что полученный исполняемый файл с оптимизацией по умолчанию -O2 был самым быстрым, который я видел, иногда достигая 184 мсек. за входной файл. С этим одним изменением к исходному коду соответствующие результаты были такими;
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.7.2 | 234 | 234 |1.00 |
----------|-----|-----|-----|
Clang-3.2 | 188 | 187 |1.00 |
----------|-----|-----|------
GCC/Clang |1.24 |1.25 |
Примечания здесь:
- Ни один из компиляторов теперь не получает никакой выгоды от оптимизации -O3.
- Clang побеждает GCC так же важно на каждом уровне оптимизации.
- На производительность GCC лишь незначительно влияет изменение типа интеллектуального указателя.
- На производительность Clang -O2 существенно влияет изменение типа интеллектуального указателя.
До и после изменения типа интеллектуального указателя Clang может создать существенно более быстрый исполняемый файл coan при оптимизации -O3, и он может создать одинаково более быстрый исполняемый файл при -O2 и -O3, когда этот тип указателя является лучшим - std::shared_ptr<X>
- для работы.
Очевидный вопрос, который я не компетентен комментировать, заключается в том, почему Clang должен быть в состоянии найти ускорение на 25% -O2 в моем приложении, когда интенсивно используемый тип интеллектуального указателя изменяется с уникального на общий, в то время как GCC безразличен к тому же изменению. Также я не знаю, стоит ли мне подбадривать или приветствовать открытие, что оптимизация Clang -O2 таит в себе такую огромную чувствительность к мудрости моих умных указателей.
ОБНОВЛЕНИЕ: GCC 4.8.1 v clang 3.3
Соответствующие результаты сейчас:
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.1 | 442 | 443 |1.00 |
----------|-----|-----|-----|
Clang-3.3 | 374 | 370 |1.01 |
----------|-----|-----|------
GCC/Clang |1.18 |1.20 |
Тот факт, что все четыре исполняемых файла теперь занимают намного большее среднее время, чем ранее, для обработки файла 1, не отражается на производительности последних компиляторов. Это связано с тем, что более поздняя ветвь разработки тестового приложения в то же время приобрела большую сложность в разборе и платит за это быстротой. Только отношения являются значительными.
Примечания теперь не являются захватывающими нововведениями:
- GCC безразличен к оптимизации -O3
- Clang очень незначительно выигрывает от оптимизации -O3
- Clang побеждает GCC с одинаково важным преимуществом на каждом уровне оптимизации.
Сравнивая эти результаты с результатами для GCC 4.7.2 и clang 3.2, выявляется, что GCC вернул себе примерно четверть лидерства clang на каждом уровне оптимизации. Но поскольку тестовое приложение в настоящее время интенсивно разрабатывается, нельзя с уверенностью отнести это к наверстыванию в процессе генерации кода в GCC. (На этот раз я отметил снимок приложения, из которого были получены тайминги, и могу использовать его снова.)
ОБНОВЛЕНИЕ: GCC 4.8.2 v clang 3.4
Я закончил обновление для GCC 4.8.1 v Clang 3.3, сказав, что я буду придерживаться того же снимка коана для дальнейших обновлений. Но вместо этого я решил протестировать этот снимок (версия 301) и последний имеющийся у меня снимок разработки, который проходит свой набор тестов (версия 619). Это дает результаты немного долготы, и у меня был другой мотив:
В моей первоначальной публикации отмечалось, что я не тратил усилий на оптимизацию коана по скорости. Это было все еще в случае с Rev. 301. Однако после того, как я встроил устройство синхронизации в жгут для проверки коанов, каждый раз, когда я запускал набор тестов, влияние последних изменений на производительность меня поразило. Я видел, что он часто был удивительно большим и что тенденция была более крутой, чем я чувствовал, чтобы быть заслуженным ростом функциональности.
Рев. 308 среднее время обработки каждого входного файла в наборе тестов более чем удвоилось с момента первой публикации здесь. В тот момент я сделал разворот на своей 10-летней политике не заботиться о производительности. При интенсивном потоке пересмотров всегда учитывалось до 619 производительности, и большое количество из них было направлено исключительно на переписывание ключевых несущих нагрузки на принципиально более быстрых линиях (хотя без использования каких-либо нестандартных функций компилятора для этого). Было бы интересно увидеть реакцию каждого компилятора на этот разворот,
Вот теперь знакомая матрица таймингов для последних двух сборок rev.301:
Coan - Rev.301 результаты
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 428 | 428 |1.00 |
----------|-----|-----|-----|
Clang-3.4 | 390 | 365 |1.07 |
----------|-----|-----|------
GCC/Clang | 1.1 | 1.17|
История здесь только незначительно изменилась с GCC-4.8.1 и Clang-3.3. Показ GCC немного лучше. У Кланга это немного хуже. Шум вполне может объяснить это. Clang по-прежнему выходит вперед -O2
а также -O3
поля, которые не будут иметь значения в большинстве приложений, но будут иметь значение для многих.
А вот и матрица для ред. 619.
Coan - rev.619 результаты
| -O2 | -O3 |O2/O3|
----------|-----|-----|-----|
GCC-4.8.2 | 210 | 208 |1.01 |
----------|-----|-----|-----|
Clang-3.4 | 252 | 250 |1.01 |
----------|-----|-----|------
GCC/Clang |0.83 | 0.83|
Принимая цифры 301 и 619 бок о бок, высказываются несколько моментов.
Я стремился написать более быстрый код, и оба компилятора решительно оправдывают мои усилия. Но:
GCC воздает эти усилия гораздо щедрее, чем Clang. В
-O2
Оптимизация сборки 619 Clang на 46% быстрее, чем сборка 301: в-O3
Улучшение Clang составляет 31%. Хорошо, но на каждом уровне оптимизации сборка 619 в GCC более чем в два раза быстрее, чем в 301.GCC более чем отменяет прежнее превосходство Clang. И на каждом уровне оптимизации GCC теперь опережает Clang на 17%.
Способность Clang в сборке 301 получить больше рычагов, чем GCC от
-O3
оптимизация пропала в 619 билде. Ни один из компиляторов не получает значимого от-O3
,
Я был достаточно удивлен этим изменением состояния, которое я подозревал, что я мог случайно сделать медленную сборку самого clang 3.4 (так как я собрал его из исходного кода). Таким образом, я повторно запустил тест 619 с запасом Clang 3.3 моего дистрибутива. Результаты были практически такими же, как и для 3.4.
Итак, что касается реакции на разворот: по числам, приведенным здесь, Clang добился гораздо больших успехов, чем GCC, по скорости выжимания из моего кода C++, когда я не помогал. Когда я решил помочь, GCC проделал намного лучшую работу, чем Clang.
Я не превращаю это наблюдение в принцип, но извлекаю урок: "Какой компилятор создает лучшие двоичные файлы?" это вопрос, который, даже если вы указываете набор тестов, к которому ответ должен быть относительным, все еще не является четким вопросом просто синхронизации двоичных файлов.
Ваш лучший двоичный файл - самый быстрый двоичный файл, или это тот, который лучше всего компенсирует дешевый код? Или лучше всего компенсирует дорогостоящий код, который отдает предпочтение удобству обслуживания и повторному использованию по скорости? Это зависит от характера и относительных весов ваших мотивов для создания двоичного файла, а также от ограничений, при которых вы делаете это.
И в любом случае, если вы глубоко заботитесь о создании "лучших" двоичных файлов, вам лучше проверять, как последовательные итерации компиляторов реализуют вашу идею "лучших" по сравнению с последовательными итерациями вашего кода.
Phoronix сделал несколько тестов по этому поводу, но речь идет о снэпшот-версии Clang/LLVM, выпущенной несколько месяцев назад. Результатом было то, что вещи были более или менее толчком; ни GCC, ни Clang не являются окончательно лучше во всех случаях.
Так как вы использовали бы последний Clang, возможно, он немного менее актуален. С другой стороны, в GCC 4.6 запланированы некоторые важные оптимизации для Core 2 и i7, по-видимому.
Я полагаю, что более быстрая скорость компиляции Clang будет лучше для оригинальных разработчиков, а затем, когда вы отправите код в мир, Linux distro/BSD/ и т.д. конечные пользователи будут использовать GCC для более быстрых двоичных файлов.
Тот факт, что Clang компилирует код быстрее, может быть не так важен, как скорость получающегося двоичного файла. Тем не менее, вот ряд тестов.
Общая разница между GCC 4.8 и clang 3.3 очень мала с точки зрения скорости получаемого двоичного файла. В большинстве случаев код, сгенерированный обоими компиляторами, работает аналогично. Ни один из этих двух компиляторов не доминирует над другим.
Тесты, свидетельствующие о существенном разрыве в производительности между GCC и clang, являются случайными.
На производительность программы влияет выбор компилятора. Если разработчик или группа разработчиков используют исключительно GCC, можно ожидать, что программа будет работать немного быстрее с GCC, чем с clang, и наоборот.
С точки зрения разработчика, заметная разница между GCC 4.8+ и clang 3.3 заключается в том, что GCC имеет -Og
опция командной строки. Эта опция включает оптимизацию, которая не мешает отладке, поэтому, например, всегда можно получить точные трассировки стека. Отсутствие этой опции в clang затрудняет использование clang в качестве оптимизирующего компилятора для некоторых разработчиков.
Особое отличие, которое я заметил в gcc 5.2.1 и clang 3.6.2, заключается в том, что если у вас есть такой критический цикл, как:
for (;;) {
if (!visited) {
....
}
node++;
if (!*node) break;
}
Тогда GCC будет при компиляции с -O3
или же -O2
, умозрительно разверните петлю восемь раз. Clang не развернет его вообще. Методом проб и ошибок я обнаружил, что в моем конкретном случае с данными моей программы правильное количество развертываний равно пяти, поэтому gcc overshot и clang undershot. Тем не менее, перерасход был более вредным для производительности, поэтому gcc показал себя намного хуже.
Я понятия не имею, является ли разница в развертывании общей тенденцией или это что-то особенное для моего сценария.
Некоторое время назад я написал несколько сборщиков мусора, чтобы больше узнать об оптимизации производительности в C. И полученных результатов, на мой взгляд, достаточно, чтобы немного отдать предпочтение клэнгу. Тем более что сборка мусора в основном связана с погоней за указателями и копированием памяти.
Результаты (числа в секундах):
+---------------------+-----+-----+
|Type |GCC |Clang|
+---------------------+-----+-----+
|Copying GC |22.46|22.55|
|Copying GC, optimized|22.01|20.22|
|Mark & Sweep | 8.72| 8.38|
|Ref Counting/Cycles |15.14|14.49|
|Ref Counting/Plain | 9.94| 9.32|
+---------------------+-----+-----+
Это все чистый код C, и я не претендую на производительность обоих компиляторов при компиляции кода C++.
В Ubuntu 15.10, x86.64 и процессоре AMD Phenom(tm) II X6 1090T.
Единственный способ определить это - попробовать. Я видел некоторые действительно хорошие улучшения при использовании Apple LLVM gcc 4.2 по сравнению с обычным gcc 4.2 (для кода x86-64 с довольно большим количеством SSE), но YMMV для разных кодовых баз. Если вы работаете с x86/x86-64 и действительно заботитесь о последних нескольких процентах, тогда вам следует попробовать Intel ICC, поскольку это часто может побить gcc - вы можете получить 30-дневную пробную лицензию от intel.com и попробуй это.
Собственно говоря, ответ: это зависит. Существует множество тестов, ориентированных на различные виды приложений.
Мой тест в моем приложении: gcc> icc> clang.
Есть редкие операции ввода-вывода, но многие операции с плавающей запятой процессора и структура данных.
флаги компиляции: -Wall -g -DNDEBUG -O3.
https://github.com/zhangyafeikimi/ml-pack/blob/master/gbdt/profile/benchmark