Анимация
JavaScript
|
Главная Библионтека int main О { const double temp = 3.14159 * 2.71828; double d = temp * temp; Такое встраивание устраняет излишние расходы на выполнение вызова функции, а именно - на внесение параметров в стек, переход процессором в другое место памяти для выполнения кода функции, что, помимо затрат на переход, может привести к полному или частичному сбросу кэша инструкций процессора. Встраивание - это далеко не то же, что и трактовка Square как макроса, поскольку вызов встраиваемой функции остается вызовом функции, и ее аргументы вычисляются только один раз. В случае же макросов вычисление аргументов может выполняться неоднократно; так, макрос #define SquareMacro(x) С(х)*(х)) при вызове SquareMacro(3.14159"2.71828) будет раскрыт до (3.14159*2.71828)*(3.14159*2.71828) (т.е. умножение п на е будет выполнено не один раз, а два). Заслуживает внимания еще один частный случай - рекурсивный вызов, когда функция вызывается из самой себя, непосредственно или опосредованно. Хотя такие вызовы зачастую не могут быть встраиваемыми, в некоторых случаях компилятор может сделать рекурсию встраиваемой так же, как может частично разворачивать некоторые циклы. Между прочим, вы заметили, что в примере 25-1, который и-ллюстрирует встраиваемость, не использовано ключевое слово inline? Это сделано преднамере]Пто. Мы вернемся к этому моменту сшс не раз в процессе рассмотрения основного вопроса задачи. 2. Когда выполняется встраивание? Может ли оно выполняться; а) во время написания исходного текста? б) во время компиляции? в) во время компоновки? г) при инсталляции приложения? д) в процессе работы? е) в некоторое другое время? 3. Дополнительный вопрос: какого рода функции гарантированно не будут встраиваемыми? Ответ А: во время написания исходного текста В процессе написания исходного текста разработчики могут использовать ключевое слово inline в своих программах. Это не является реальным выполнением встраивания в смысле перемещения кода для устранения вызова функции, но эго попытка выбрать и явно выделить подходящие места для встраивания функции, так что мы будем рассматривать ее как наиболее раннюю возможность принятия решения о встраивании*. Когда вы намереваетесь написать ключевое слово inline в вашем исходном тексте, вы не должны забывать о трех важных вещах. • По умолчанию не делайте этого. Преждевременная оптимизация - зло, и вы не должны использовать inline до тех пор, пока профилирование не покажет необходимость этого в определенных случаях. Более полную информацию по этому вопросу можно найти в [Sutterf)2] или запросив на поисковом сервере типа Google что-то вроде "преждевременная оптимизация" или "premature Еше одна интерпретация "во время написания исходного текста" может заключаться в буквальном встраивании функций некоторыми разработчиками путем физического перемещения блоков исходного текста. Это действие еще больше отдаляет нас от обычного понимания термина "встраивание", так что я не буду его здесь рассматривать. site:www.gotw.ca" - вы получите массу страшных предупреждений о прежде-временной оптимизации вообще и встраивании в частности. • Это означает всего лишь "попробуйте, пожалуйста". Как описано в [Sutter02], ключевое слово inline -- всего лишь подсказка для компилятора, возможность попытаться мило поговорить с ним, предоставляемая языком программирования (далее будут описаны недостатки таких "милых" разговоров). Ключевое слово inline вообще не имеет никакого семантического действия в программе на С++. Оно не влияет на другие конструкции языка, на использование функции, объявленной inline (например, вы можете получить адрес такой функции), и нет никакой стандартной возможности программно определить, объявлена ли данная функция как встраиваемая или нет. • Это часто делается не на требуемом уровне детализации. Мы пишем ключевое слово inline для функции, но когда выполняется встраивание, то в действительности оно происходит при вызове функции. Это отличие очень важно, поскольку одна и та же функция может (и зачастую должна) быть встраиваемой в одном месте вызова, но не в некотором другом. Ключевое слово inline не дает вам никакой возможности выразить этот факт, поскольку мы можем указать, что встраиваемой является функция сама по себе, что эквивалентно неявному указанию делать эту функнию встраиваемой везде, во всех возможных местах вызова. Такое предвидение редко бывает точным. Так что хотя в разговоре мы часто говорим о "встраиваемой функции", более точно было бы говорить о встраиваемом вызове функции. > Рекомендация Избегайте использования ключевого слова inline или других попыток оптимизации до тех пор, пока на их необходимость не укажут измерения производительности программы. Ответ Б: во время компиляции Обычно во время работы компиляторы сами, без внешней подсказки, выполняют описанный в примере 25-1 вид встраивания. Что делает компилятор, когда мы мило разговариваем с ним путем объявления некоторых функций встраиваемыми? Это зависит от ситуации. Не все KOMnHJMTOpbi хорошо поддерживают такие "милые" разговоры, даже если задаривать их шоколадом и цветами. Ваш компилятор запросто может проигнорировать вашу просьбу, и даже не одним, а тремя способами. • Ие делая встроенными вызовы функций, которые вы объявили как inline. • Делая встроенными вызовы функций, которые вы не объявляли встраиваемыми. • Встраивая некоторые из вызовов, оставляя другие вызовы той же функции обычными невстраивасмыми (независимо от того, объявлена ли функция как inline). Вернемся к примеру 25-1 и обратим внимание на то, что в нем нигде не говорится, что функция должна быть встраиваемой. Это сделано преднамеренно, потому что этим я хотел проиллюстрировать тот факт, что встраивание все равно может произойти, даже без объявления функции i nl i пе. Не удивляйтесь, если ваш сегодняшний компилятор поступит именно так. Поскольку вы не можете написать соответствующую стандарту программу, которая выявит отличия при встраивании функции компилятором, этот способ оптимизации оказывается совершенно законным, и компилятор может (а часто и должен) выполнять его за вас. Обычно современные компиляторы в состоянии лучше программиста решить, какие вызовы функций следует сделать встраиваемыми, а какие - нет, включая ситуации, когда одна и та же функция в разных местах может быть обработана по-разному. Почему? Простейшая причина в том, что компилятор знает больше о контексте вызова, поскольку ему известна "реальная" структура точки вызова - машинный код, сгенерированный для данной точки после применения других оптимизаций, таких как разворачивание циклов или удаление недостижимых ветвей профаммы. Например, компилятор может быть способен определить, что встраивание функции в некотором впугреннем цикле может сделать цикл слишком большим для размещения в кэше процессора, что приведет только к падению производительности, так что такой вызов не будет сделан встроенным, в то время как другие вызовы той же функции в других местах программы могут остаться встроенными. Ответ В: во время компоновки Теперь мы переходим к более интересным и более современным аспектам встраивания. Вопрос: может ли функция быть встроена во время компиляции? Ответ: да. Этот ответ является основой дополнительного вопроса о функциях, которые не могут быть встраиваемыми ни при каких условиях. Этот вопрос добавлен мною в задачу, потому что обычно все верят, что такие виды функций сушествуют. В частности, обычно считается, что невозможно встроить функции, описания которых размещено в отдельном модуле, а не в заголовочном файле. Давайте попытаемся выполнять встраивание максимально интенсивно, насколько это возможно. Рассмотрим пример 25-1 с небольшим изменением. пример 25-2: затрудним работу оптимизатора, поместив функцию в отдельный модуль, и сделав недоступным ее исходное описание. - Файл main, срр - double SquareC double х ); int main О { double d = SquareC 3.14159 * 2.71828 ); - Файл square.obj (или .о) - содержит скомпилированное описание функции double SquareC double х ) { return х * х; } Здесь идея заключается в том, что реализация функции Square вынесена из единицы трансляции mai п. срр. На самом деле сделано даже больше: недоступны даже исходные тексты функции Square - только се объектный код. "Теперь вызов Square гарантированно не будет встраиваемым!" - скажет множество людей. Пока идет компиляция - они правы. В процессе компиляции mai п. срр никакой самый мощный и продвинутый компилятор не в состоянии получить доступ к исходным текстам определения функции Square. На вот продвинутый компоновщик с такой задачей справиться в состоянии, и некоторые популярные реализации так и посгупают. Ряд компиляторов, в частности, Hewlett-Packard, поддерживают такое межмодульное встраивание (cross-module inlining); в Microsoft Visual С++ 7.0 (известном как ".NET") и более поздних версий имеется опция /ltcg, которая означает "link time code generation" (генерация кода в процессе компиляции). Реальное преимущество такой технологии "позднего встраивания" заключается в знании реального контекста каждой точки вызова, что 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 [ 51 ] 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |