Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Почему функции которые содержат циклы, рекурсию или вызываются по указателю, плохо подвержены встраиванию (inline)

#cpp #функции #циклы #рекурсия #inline


Почему функции которые содержат циклы, рекурсию или вызываются по указателю, плохо
подвержены встраиванию (inline)
    


Ответы

Ответ 1



Вопрос построен на терминологической путанице. Встраивание вызовов функции - это не свойство самой функции, а всегда именно свойство индивидуального вызова функции. Свойства самой функции, разумеется, играют роль в принятии решения о том, будет ли конкретный вызов встроен, но все равно в общем случае это решение принимается для каждого отдельного вызова индивидуально. Одни вызовы функции могут оказаться встроены, в то время как другие вызовы той же функции могут оказаться не встроены. Даже аргументы функции, указанные в конкретном вызове, могут влиять на решение о том, встраивать ли этот конкретный вызов. Утверждение о том, что вызовы функций с циклами не встраиваются, относится к категории "это было давно и неправда". Каждый компилятор реализует какой-то набор эвристических критериев, который дает ему возможность принимать общие решения о том, заслуживают ли вызовы данной функции встраивания. В каком-то из старинных компиляторов этот эвристический критерий также включал проверку тела функции на наличие циклов (Borland или что-то в этом роде). На самом деле циклы не представляют из себя никаких преград для встраивания. Мне не известны никакие современные реализации, которые бы отказывались встраивать вызовы функций с циклами внутри. Утверждение о том, что функции, которые вызываются через указатель, не встраиваются - это как раз яркий пример того терминологической путаницы, о которой я писал выше. Это вызовы, выполненные через указатель, в общем случае не встраиваются. В то же время вызовы той же самой функции, выполненные напрямую, могут прекрасно встраиваться. Почему в общем случае невозможно встроить вызов, сделанный через указатель, очевидно: потому что на стадии компиляции не ясно, какая функция будет вызываться. Однако если компилятор в состоянии сообразить на стадии компиляции, какая функция будет вызвана через указатель, он без проблем встроит и такой вызов. Классический часто задаваемый вопрос, основанный на данной терминологической путанице - вопрос о встраивании вызовов виртуальных методов классов. Они ведь вызываются "через указатель" и значит не могут быть встроены, правда? Не правда. Во-первых, сам пользователь может выполнить вызов виртуального метода напрямую, без использования виртуального механизма. Во-вторых, в огромном количестве контекстов сам компилятор в состоянии сообразить, какой конкретный метод вызывается в данном вызове и, соответственно, встроить этот вызов. Вызовы рекурсивных функций тоже могут встраиваться. Если компилятор на стадии компиляции в состоянии оценить максимальную глубину рекурсии, то он может встроить эти вызовы ("развернуть рекурсию") на всю глубину. Если компилятор не в состоянии выполнить такой оценки, то он может встроить рекурсивные вызовы до определенной фиксированной глубины, после чего выполнить обычный рекурсивный вызов. Встраивание вызовов рекурсивных функции в этом отношении во многом аналогично развертке циклов. Одной из простейших и наиболее очевидных ситуаций целесообразности встраивания вызова функции является, например, ситуация, когда заведомо известно, что некоторая функция вызывается в программе ровно один раз (в пространственном смысле, т.е. исходный код программы содержит ровно одно место с ее вызовом). Такой функции нет никакого смысла существовать в виде отдельной функции, независимо от того, как "тяжела" эта функция. (С этим идет ряд побочных оговорок, но к данной теме они не относятся.) Например, если функция имеет внутреннее связывание и вызывается в своей единице трансляции ровно один раз, то современные компиляторы встроят этот вызов независимо от каких-либо иных критериев. В компиляторе GCC за это отвечает опция -finline-functions-called-once, которая включается уже в режиме -O1. В качестве другого примера, в компиляторе MSVC++ есть #pragma-параметры inline_recursion и inline_depth, которые управляют встраиванием рекурсивных функций и глубиной развертки рекурсии.

Ответ 2



Ну, с рекурсией понятно - если она не преобразована компилятором в цикл, то что получится? встраиваем код, на месте ее вызова опять встраиваем код, в котором на месте вызова... Сколько раз код встраивать? :) С вызовом по указателю - опять же: если встроена, то функция не имеет четкого пролога-эпилога, и не может быть вызвана по адресу - так что при вызове через указатель должен иметься не встроенный экземпляр функции как минимум - чтобы было, что вызывать. В самом месте вызова через указатель встроить ничего нельзя - так как встраивает компилятор, а во время компиляции значение указателя неизвестно. А про циклы - мне кажется, тут вы что-то напутали. Функция с циклом внутри вполне встраиваема - если, конечно, это имеет смысл :) Если вы имеете в виду, что сам цикл раскручивается - то опять же, это делается, но со своими ограничениями - например, компилятор может не знать, сколько реально итераций будет сделано - так как ему цикл разворачивать?..

Комментариев нет:

Отправить комментарий