В настоящее время я испытываю какой-то странный эффект с gcc
(проверенная версия: 4.8.4).
У меня есть ориентированный на производительность код, который работает довольно быстро. Его скорость зависит в значительной степени от наложения многих мелких функций.
Поскольку вложение в несколько файлов .c
затруднено (-flto
пока еще не широко доступно), я сохранил множество небольших функций (обычно от 1 до 5 строк кода каждый) в общий файл C, в который я разрабатываю кодек и связанный с ним декодер. Он "относительно" большой по моему стандарту (около ~ 2000 строк, хотя многие из них - просто комментарии и пустые строки), но разбивка на мелкие части открывает новые проблемы, поэтому я бы предпочел избежать этого, если это возможно.
Кодер и декодер связаны друг с другом, поскольку они являются обратными операциями. Но с точки зрения программирования они полностью разделены, не разделяя ничего общего, за исключением нескольких функций typedef и очень низкого уровня (например, чтение из позиции без позиции).
Странный эффект заключается в следующем:
Недавно я добавил новую функцию fnew
к кодеру. Это новая "точка входа". Он не используется и не вызывается нигде внутри файла .c
.
Простой факт, что он существует, делает производительность функции декодера fdec
существенно снижающейся более чем на 20%, что слишком сложно игнорировать.
Теперь имейте в виду, что операции с кодировкой и декодированием полностью разделены и не имеют ничего общего, сохраните некоторые незначительные typedef
(u32
, u16
и т.д.) и связанные с ним операции (чтение/запись).
При определении новой функции кодирования fnew
как static
производительность декодера fdec
увеличивается до нормального. Поскольку fnew
не вызывается из .c
, я предполагаю, что он такой же, как если бы он не был (удаление мертвого кода).
Если теперь static fnew
вызывается со стороны кодировщика, производительность fdec
остается сильной.
Но как только fnew
будет изменен, производительность fdec
существенно снизится.
Предполагая, что модификации fnew
пересекли порог, я увеличил следующий параметр gcc
: --param max-inline-insns-auto=60
(по умолчанию его значение должно быть 40.) И это сработало: производительность fdec
теперь возвращается к нормальная.
И я думаю, что эта игра будет продолжаться вечно с каждой небольшой модификацией fnew
или что-то еще похожее, требующее дальнейшей настройки.
Это просто странно. Нет никакой логической причины для некоторой небольшой модификации функции fnew
, чтобы иметь эффект детонации на полностью несвязанной функции fdec
, которая должна быть только в одном файле.
Единственное предварительное объяснение, которое я мог бы изобрести до сих пор, состоит в том, что, возможно, простого присутствия fnew
достаточно, чтобы пересечь какой-то global file threshold
, который повлияет на fdec
. fnew
можно сделать "нет", если это: 1. не существует, 2. static
, но не вызывается из любой точки 3. static
и достаточно мал, чтобы быть встроенным. Но это просто скрывает проблему. Означает ли это, что я не могу добавить какую-либо новую функцию?
Действительно, я не мог найти ни одного удовлетворительного объяснения в любой точке сети.
Мне было интересно узнать, кто-то уже испытал некоторый эквивалентный побочный эффект, и нашел для него решение.
[изменить]
Отпустите еще один сумасшедший тест.
Теперь я добавляю еще одну совершенно бесполезную функцию, просто чтобы поиграть. Его содержимое строго точно является копией-парой fnew
, но имя функции, очевидно, отличается, поэтому позвоните ему wtf
.
Когда wtf
существует, не имеет значения, статичен ли /t 23 > или нет, а что значение max-inline-insns-auto
: производительность fdec
вернется к норме.
Даже если wtf
не используется и не вызывается из любого места...: '(
[Редактировать 2]
нет инструкции inline
. Все функции являются либо нормальными, либо static
. Решение о встраивании находится исключительно в области компилятора, который до сих пор работал нормально.
[Редактировать 3]
Как предложил Питер Кордес, проблема не связана с встроенным, а с выравниванием команд. На более позднем процессоре Intel (Sandy Bridge и более поздних версиях) преимущество использования горячей линии от выравнивания по 32-байтным границам.
Проблема заключается в том, что по умолчанию gcc
выравнивает их по 16-байтным границам. Это дает 50% шанс на правильное выравнивание в зависимости от длины предыдущего кода. Поэтому трудно понять проблему, которая "выглядит случайной".
Не все петли чувствительны. Это имеет значение только для критических циклов, и только если их длина заставляет их пересекать еще один 32-байтовый сегмент команд при менее идеальном выравнивании.