Избегайте накладных расходов протектора gcc?
В последнее время я столкнулся с множеством функций, где gcc генерирует действительно плохой код на x86. Все они соответствуют шаблону:
if (some_condition) {
/* do something really simple and return */
} else {
/* something complex that needs lots of registers */
}
Подумайте о простом случае как о чем-то таком маленьком, что половина или больше работы тратится на толкание и выскакивание регистров, которые вообще не будут изменены. Если бы я писал asm вручную, я бы сохранил и восстановил регистры с сохраненными перекрестными вызовами внутри сложного случая и вообще не касался указателя стека в простом случае.
Есть ли способ заставить gcc быть немного умнее и сделать это сам? Предпочтительно с параметрами командной строки, а не уродливыми хаками в источнике...
Изменить: Чтобы сделать его конкретным, здесь что-то очень близко к некоторым из функций, с которыми я имею дело:
if (buf->pos < buf->end) {
return *buf->pos++;
} else {
/* fill buffer */
}
и еще один:
if (!initialized) {
/* complex initialization procedure */
}
return &initialized_object;
а другой:
if (mutex->type == SIMPLE) {
return atomic_swap(&mutex->lock, 1);
} else {
/* deal with ownership, etc. */
}
Изменить 2: Я должен был упомянуть для начала: эти функции не могут быть встроены. У них внешняя связь, и они являются библиотечным кодом. Предоставление им возможности быть встроенным в приложение приведет к возникновению всех проблем.
Ответы
Ответ 1
Возможно, обновите версию gcc? 4.6 только что был выпущен. Насколько я понимаю, у него есть возможность "частичного inline". То есть легко интегрируемая внешняя часть функции встроена, и дорогая часть преобразуется в вызов. Но я должен признать, что я сам не пробовал.
Изменить: Утверждение, которое я имел в виду из ChangeLog:
Частичная вставка теперь поддерживается и включен по умолчанию при -O2 и выше. Функцию можно контролировать с помощью -fpartial-встраивание.
Частичная вставка разделяет функции с короткий горячий путь для возврата. Это позволяет более агрессивная инкрустация горячей путь ведущий для повышения производительности и часто для уменьшения размера кода (потому что холодные части функций не дублируется).
...
Встраивание при оптимизации размера (либо в холодных регионах программы или при компиляции с -О) улучшено, чтобы лучше справляться с программами на С++ с большим штрафом за абстракцию, что приводит к меньшему и более быстрому коду.
Ответ 2
Обновление
Чтобы явно подавить вложение для одной функции в gcc, используйте:
void foo() __attribute__ ((noinline))
{
...
}
См. также Как я могу сказать gcc не встраивать функцию?
Функции, подобные этому, будут автоматически включаться автоматически, если не скомпилированы -O0 (отключить оптимизацию).
В С++ вы можете намекнуть на компилятор, используя ключевое слово inline
Если компилятор не будет использовать ваш намек, вы, вероятно, используете слишком много регистров/ветвей внутри функции. Ситуация почти наверняка решена путем извлечения "сложного" блока в его собственную функцию.
Обновить, я заметил, что вы добавили факт, что они являются внешними символами. (Пожалуйста, уточните вопрос с этой важной информацией). Ну, в некотором смысле, с внешними функциями, все ставки отключены. Я не могу поверить, что gcc по определению встроит всю сложную функцию в крошечного вызывающего абонента просто потому, что она только вызывается оттуда. Возможно, вы можете дать пример кода, демонстрирующий поведение, и мы можем найти правильные флаги оптимизации, чтобы исправить это?
Также, это C или С++? В С++ я знаю, что распространенное место для включения тривиальных функций решения inline (в основном как члены, определенные в объявлении класса). Это не приведет к конфликту связи, как с простыми (внешними) функциями C.
Также вы можете определить определенные функции шаблонов, которые будут полностью встроены во все модули компиляции, не приводя к конфликтам ссылок.
Надеюсь, вы используете С++, потому что здесь вы получите массу вариантов.
Ответ 3
Я бы сделал это следующим образом:
static void complex_function() {}
void foo()
{
if(simple_case) {
// do whatever
return;
} else {
complex_function();
}
}
Компилятор мой настаивает на inlining complex_function(), и в этом случае вы можете использовать на нем атрибут noinline.
Ответ 4
Я бы, вероятно, реорганизовал код, чтобы поощрить вложение простого случая. Тем не менее, вы можете использовать -finline-limit
, чтобы сделать gcc
рассмотрением вложения больших функций или -fomit-frame-pointer -fno-exceptions
, чтобы минимизировать фрейм стека. (Обратите внимание, что последний может нарушить отладку и вызвать исключения С++ для плохой работы.)
Вероятно, вы не сможете многое получить от настройки параметров компилятора, и вам придется реорганизовать.
Ответ 5
Увидев, что это внешние вызовы, возможно, что gcc рассматривает их как небезопасные и сохраняет регистры для вызова функции (трудно узнать, не видя, что сохраняемые регистры, в том числе те, которые вы говорите, не используются "). Из любопытства, происходит ли чрезмерное переполнение реестра при отключении всех отключений?