>>> но почему тогда в тестах GCC не используют флаг -O3?
>> По той же причине, по которой не используют -Ofast.-Ofast не используют, потому что он обеспечивает оптимизации, не соответствующие стандарту.
Не всем и не всегда важно строгое соответствие IEEE 754.
> -O3 этим не отличается.
Ну. Так если он не приводит к нарушению IEEE 754, а его всё равно не используют — в этом что-то есть, не так ли?
>> В GCC включаемые с -O3 оптимизации на практике редко дают сколько-нибудь существенный прирост производительности,
> Хотелось бы увидеть замеры хотя бы на тех же синтетических тестах.
Замечательно. Возьмите хоть SPEC, хоть что хотите, сделайте замеры и поделитесь с нами результатами.
> Когда-то давно, когда я сидел на Gentoo, я разницу очень даже чувствовал.
У меня с gcc 4.9:
% gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
% gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
% diff /tmp/O2-opts /tmp/O3-opts | grep enabled
> -fgcse-after-reload [enabled]
> -finline-functions [enabled]
> -fipa-cp-clone [enabled]
> -fpredictive-commoning [enabled]
> -ftree-loop-distribute-patterns [enabled]
> -ftree-loop-vectorize [enabled]
> -ftree-partial-pre [enabled]
> -ftree-slp-vectorize [enabled]
> -funswitch-loops [enabled]
Половина из них копирует базовые блоки или циклы, увеличивая размер кода, так, что теоретический прирост производительности нивелируется возрастанием задержек на более частую перезагрузку линий кэша команд. Вторая половина — та, что относится к Graphite — в реальной жизни просто не работает.
Не поймите меня неправильно, эти оптимизации не бесполезны — когда у вас есть одна или несколько однотипных машин, конфигурация которых не изменяется годами, и одна или несколько специализированных программ, выполняющих на этих машинах расчёты продолжительностью неделю-две на одном наборе данных. Тогда даже несколько процентов ускорения сыграют роль, а программы могут быть написаны таким образом, что они получат реальное ускорение от этих конкретных оптимизаций. Но это совершенно иррелевантно в случае вашей домашней машины с Gentoo и 700 установленными программами, наиболее используемые из которых — текстовый редактор, браузер, почтовый клиент — интерактивны, и большую часть времени простаивают на ожидании ввода-вывода.
>> но значительно (относительно) увеличивают размер кода, что негативно сказывается на его
>> локальности в I$.
> Что такое I$?
Instruction cache.
>>> Если в LLVM использовался его хвалёный JIT
>> Херню сморозил.
> Не понимаю, поясните.
LLVM одинаково хорошо (или одинаково плохо; главное — одинаково) позволяет строить на своей основе как JIT-, так и AOT-трансляторы.
Когда LLVM используется для кодогенерации для Radeon в драйвере, он работает как JIT: драйвер непрерывно формирует поток команд, которые непрерывно транслируются LLVM в другой поток. Другой гипотетический пример: когда исполняется программа на JavaScript, транслятор непрерывно читает из буфера программный код (на JavaScript или байт-код, не суть), который должен исполниться прямо сейчас, и непрерывно транслирует его в машинный код (или загружает уже оттранслированный код из кэша, но это не важно). Ключевое слово — «непрерывное исполнение», и в этот процесс вовлечён транслятор.
Когда LLVM используется фронтэндом Clang для трансляции кода на C или C++, он работает точно так же, как GCC: фронтэнд читает программу на ЯВУ и формирует промежуточное представление, которое затем понижается до машинного кода (записываемого компоновщиком в исполняемый файл), транслируется в другой ЯВУ — опять же, не суть. С выхода Clang (то есть, LLVM; то есть, компоновщика) вы получаете точно такой же исполняемый файл, как и с выхода GCC. Этот файл может быть скомпонован с libc, другими указанными вами библиотеками, но транслятор в его последующем исполнении никакого участия не принимает. Это ELF (или Mach-O, или COFF) с записанным в него машинным кодом.