#c #compiler-optimization #side-effects #compiler-options #elision
#c #оптимизация компилятора #побочные эффекты #параметры компилятора #устранение
Вопрос:
Предположим, что мой код имеет следующую функцию:
inline int foo() {
bar();
int y = baz();
return y;
}
и предположим, что bar()
и baz()
имеют побочные эффекты.
Если я напишу:
int z = foo();
printf("z is %dn", z);
тогда, очевидно, оба bar()
и baz()
должны быть выполнены. Но если я напишу:
foo();
и не использовать возвращаемое значение, единственная причина для вызова baz()
— это его побочные эффекты.
Есть ли способ, которым я могу сообщить своему компилятору (для любого из: GCC, clang, MSVC, Intel) что-то вроде «вы можете игнорировать побочные эффекты baz()
в целях принятия решения о том, следует ли его оптимизировать»? Или «вы можете игнорировать побочные эффекты этой инструкции, чтобы решить, следует ли ее оптимизировать»?
Этот вопрос сформулирован для C и C , но может быть актуален для всех видов процедурных языков. Давайте сделаем это C , потому что это то, что я использую больше всего прямо сейчас.
Примечание:
bar()
,baz()
не находятся под моим контролем.- Подпись
foo()
не может быть изменена. Конечно, я мог бы использовать какой-нибудь прокси и применятьbaz()
лениво, если бы захотел; или просто написать другую функцию для случая неиспользованияy
. Это не то, о чем я спрашиваю.
Комментарии:
1. Вы можете передать необязательный параметр вывода функции и вызывать
baz
только в том случае, если это неNULL
так. Это, конечно, не так приятно, какint z = foo();
, но может помочь.2. Предположительно
foo
, это должна быть встроенная функция или у вас действительно агрессивный lto, в противном случае модель модели отдельной компиляции делает маловероятным, что компилятор будет предполагать что-либо о том, как вызывающий использует возвращаемое значение.3. @NateEldredge: ссылки на информацию о «pure» или «const» в качестве атрибута? Кроме того, что, если
bar()
иbaz()
не находятся под моим контролем и происходят изsome_lib.h
?4. @StoryTeller-UnslanderMonica: сделано
foo()
встроенным, хотя в противном случае вопрос был бы просто об оптимизации внутри единицы перевода.5. @Bathsheba: Это может изменить время побочных эффектов, включая их относительное время. Кроме того, примечание в нижней части вопроса.
Ответ №1:
В качестве расширения к стандартным C и C , gcc и clang поддерживают pure
атрибут function, который информирует компилятор о том, что функция не имеет побочных эффектов и ненужные вызовы могут быть удалены. Если вы объявите функцию с этим атрибутом, компилятор поверит вам, даже если вы лжете.
#include <iostream>
static int foo() __attribute__((pure));
static int foo() {
std::cout << "I am a side effect!" << std::endl;
return 17;
}
int main() {
foo();
return 0;
}
При этом вывод не выводится.Обратите внимание, что clang
это выдаст предупреждение о том, что вы игнорируете возвращаемое значение pure
функции.
(Особенно, когда вы делаете что-то подобное в C, gcc оптимизирует вызов с -O0
помощью , но оставляет его в with -O2
!)
Определение функции не обязательно должно находиться в области видимости, чтобы это работало, и вы можете повторно объявить функцию с этим атрибутом, даже если предыдущее объявление уже находится в области видимости. Таким образом, его также можно использовать для библиотечных функций. Пример.
Конечно, лгать компилятору всегда на свой страх и риск, и, возможно, это может иметь потенциальные негативные последствия, о которых я не подумал. Это может быть полезным взломом, если вы готовы тщательно изучить сгенерированный код, чтобы убедиться, что он действительно делает то, что вы хотите, но я бы не стал доверять ему слишком сильно.
Комментарии:
1.
Peculiarly, when you do
Потому что этоstatic
, оно было встроено в main доpure
того, как было рассмотрено (вот как я это понимаю). Если вы удалитеstatic
или добавитеnoinline
, он будет удален для меня.2. Если функция объявлена «чистой», несмотря на наличие побочных эффектов, ограничивается ли поведение тем, что компилятор вызывает функцию так часто или так редко, как считает нужным, или квалификатор будет рассматриваться как приглашение компилятору генерировать бессмысленный код, независимо от того, вызвал бы какой-либо из побочных эффектов в противном случаеимеет значение?
3. @supercat: Да, хороший вопрос, я не знаю. Я не знаю, что компилятор использует
pure
атрибут для чего-либо, кроме исключения ненужных вызовов, но возможно, что он делает что-то еще, что может сломаться. Я бы определенно классифицировал это как взлом, который, я думаю, неизбежен, учитывая, что вы пытаетесь заставить компилятор генерировать код, который ведет себя не так, как это обычно определено.4. @NateEldredge: Во многих случаях знание того, что компилятор будет игнорировать определенные вещи, о которых у него не должно быть причин беспокоиться, позволит программистам писать код более четко и эффективно, чем это было бы возможно в противном случае. Обработка атрибутов или квалификаторов, указывающих на определенные оптимизации, без учета того, могут ли они отрицательно повлиять на поведение программы, позволит применять их гораздо чаще, чем рассматривать их как вызывающие совершенно произвольное поведение в случаях, когда их последствия могут быть заметны.
Ответ №2:
Существует достаточное количество ситуаций, в которых побочные эффекты функции будут иметь значение только в том случае, если в конечном итоге будет использовано возвращаемое значение, но, насколько я могу судить, ни C, ни C не имеют никаких средств их указания.
В качестве общего примера может быть полезно иметь подпрограммы, которые выполняют некоторые вычисления и возвращают результаты, но вызовут ловушку, если (например, из-за переполнения) они не могут вернуть результат, который не был явно неправильным. В зависимости от того, как используется функция, ее перехват может быть бесконечно предпочтительнее, чем возврат явно неправильного ответа, но, возможно, не предпочтительнее, чтобы она вела себя так, чтобы ее поведение было неотличимо от того, чтобы каким-то образом выдать правильный ответ. Если программа выполняет дюжину вычислений, но использует только десять из них, полное пропуск неиспользуемых вычислений, вероятно, будет более эффективным, чем генерация кода для проверки, произойдет ли в них переполнение.
Возможно, полезной была бы конструкция для проверки того, может ли компилятор гарантировать, что значение определенного lvalue никогда не будет заметно влиять на поведение программы; реализации всегда будет разрешено говорить, что она не может, но в ситуациях, когда компилятор может предложить такую гарантию, это позволило бы соответствующим образом -написанный код, например, «Если значение x, которое должна вернуть эта функция, никогда не будет использоваться, не утруждайте себя его вычислением; в противном случае проверьте переполнение и перехватите, если это произойдет; если используется возвращаемое значение и переполнения не будет,вычислите значение функции. «
Однако я не знаю ни о каких реализациях C, которые предлагают такую функцию.
Комментарии:
1. Я не спрашивал о языке — я знаю ответ на это. Вопрос был только о компиляторах.