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

Могут ли компиляторы оптимизировать запросы на тривиальные функции, создаваемые с помощью указателей?

Скажем, у меня есть функция, которая принимает указатель на функцию:

int funct(double (*f)(double));

И передаю ему функцию, которая на самом деле ничего не делает:

double g(double a) { return 1.0;}
//...
funct(g);

Будет ли компилятор оптимизировать вызовы на g? Или это все еще будет иметь накладные расходы? Если у него есть накладные расходы, сколько? Достаточно ли перегрузить функцию для получения указателей на функции и постоянных значений?

4b9b3361

Ответ 1

Новые версии GCC (4.4 и новее) могут встроить и оптимизировать известный указатель функции, используя опцию -findirect-inlining. Это работает только тогда, когда GCC также знает весь код, который использует указатель.

Например, функция C-библиотеки qsort не будет пользоваться этой оптимизацией. Скомпилированный машинный код для qsort находится в библиотеке, он ожидает указатель на функцию, и компилятор не может изменить это.

Однако, если у вас была собственная реализация qsort, и вы поместили ее в файл заголовка, или вы использовали очень новые функции оптимизации времени соединения GCC, GCC сможет принять ваш код вызова, указав указанную функцию, и ваш источник qsort и скомпилируйте его вместе, оптимизированный для ваших типов данных и вашей функции сравнения.

Теперь единственное, что действительно имеет значение, - это то, что служебная нагрузка функции намного больше самой функции. В вашем примере функции, которая ничего не делает, использование указателя функции является серьезным накладным. В моем примере сравнения qsort вызов указателя функции также довольно дорого. Но в некоторых других приложениях, таких как отправка событий Windows, это не имеет большого значения.

Поскольку вы используете С++, вам следует изучить шаблоны. Функция шаблона может принимать так называемый function object, который является всего лишь объектом, реализующим operator(), и может принимать указатели на функции. Передача объекта функции позволит компилятору С++ встроить и оптимизировать почти весь задействованный код.

Ответ 2

Любой современный компилятор может (и будет) легко оптимизировать (т.е. встроенные) вызовы, выполняемые с помощью указателей функций, - это ситуации, когда компилятор знает, куда указывает указатель во время компиляции.

В вашем конкретном примере в общем случае значение указателя не может быть предсказано во время компиляции в точке вызова, поскольку оно передается извне как параметр функции funct. В ситуациях, когда сама функция funct достаточно мала, чтобы получить встроенный, параметр времени выполнения исключается, и весь код funct получает "растворенный" в контексте вызывающего абонента funct. В значении указателя известно в этом контексте вызывающего, то, опять же, компилятор может легко устранить вызов через указатель.

Наконец, если функция funct относительно тяжелая (это означает, что она не становится встроенной), вам, вероятно, следует ожидать, что компилятор будет генерировать обычный вызов во время выполнения указателя из funct. В этом случае также существует возможность устранения вызова, поскольку компилятор теоретически может генерировать отдельную версию funct для каждого значения времени компиляции g. Например, если у вас есть

int funct(int x1, int x2, int x3, double (*f)(double));

который вызывается только с двумя возможными аргументами для параметра f

funct(rand(), rand(), rand(), g1);
...
funct(rand(), rand(), rand(), g2);

тогда компилятор может "свести" его к двум функциям

int funct_g1(int x1, int x2, int x3);
int funct_g2(int x1, int x2, int x3);

без параметров указателя функции и с прямыми вызовами внутри g1 и g2 внутри. (Конечно, эта оптимизация никоим образом не привязана к указателям на функции и может применяться к любому параметру функции, который используется только с небольшим фиксированным набором аргументов. Фактически, это похоже на шаблоны С++.) Но я не зная о каких-либо компиляторах, которые бы сделали что-то подобное.

Ответ 3

Компилятор, вероятно, не оптимизирует его, потому что функция funct может получать указатели на разные функции, а не только g, и они не должны поступать из одного и того же модуля компиляции (таким образом, компилятор не может его принять знает обо всех возможных вызовах).

Вам нужно сравнить свой код, чтобы узнать, нужна ли оптимизация, о которой вы говорите, и если да, просто сделайте это. Но если funct не вызовет g много, я бы не ожидал, что это будет иметь значение.