Подтвердить что ты не робот

Избегайте накладных расходов протектора 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: Я должен был упомянуть для начала: эти функции не могут быть встроены. У них внешняя связь, и они являются библиотечным кодом. Предоставление им возможности быть встроенным в приложение приведет к возникновению всех проблем.

4b9b3361

Ответ 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 рассматривает их как небезопасные и сохраняет регистры для вызова функции (трудно узнать, не видя, что сохраняемые регистры, в том числе те, которые вы говорите, не используются "). Из любопытства, происходит ли чрезмерное переполнение реестра при отключении всех отключений?