Вот некоторые современные, хотя и узкие, мои выводы с 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 с целью дифференциации компилятора, чтобы быть в том 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 немного хуже. Шум вполне может объяснить это. 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.
Я не превращаю это наблюдение в принцип, но извлекаю урок: «Какой компилятор создает лучшие двоичные файлы?» это вопрос, который, даже если вы указываете набор тестов, к которому ответ должен быть относительным, все еще не является четким вопросом просто синхронизации двоичных файлов.
Ваш лучший двоичный файл - самый быстрый двоичный файл, или это тот, который лучше всего компенсирует дешевый код? Или лучше всего компенсирует дорогостоящий
код, который отдает предпочтение удобству обслуживания и повторному использованию по скорости? Это зависит от характера и относительных весов ваших мотивов для создания двоичного файла, а также от ограничений, при которых вы делаете это.
И в любом случае, если вы глубоко заботитесь о создании «лучших» двоичных файлов, вам лучше продолжать проверять, как последовательные итерации компиляторов воплощают вашу идею «лучших» по сравнению с последовательными итерациями вашего кода.