Я задаю этот основной вопрос, чтобы сделать записи прямо. Сослались этот вопрос а также его в настоящее время принятый ответ, что не убедительно. Тем не менее второй по популярности ответ дает лучшее понимание, но не идеально.
Читая ниже, различайте inline
ключевое слово и «встраивание» концепция.
Вот мое взятие:
Это сделано для сохранения накладных расходов на функцию. Это больше похоже на замену кода в стиле макросов. Нечего оспаривать.
inline
Ключевое слово запрос компилятору, обычно используемому для меньших функций, так что компилятор может оптимизировать его и делать более быстрые вызовы. Компилятор может игнорировать это.
Я оспариваю это по следующим причинам:
inline
ключевое слово.inline
упомянутое ключевое слово или нет.Совершенно очевидно, что пользователь не имеет никакого контроля над встраиванием функций с использованием ключевого слова. inline
,
inline
имеет ничего такого делать с концепцией встраивания. Вводinline
опережающая большая / рекурсивная функция не поможет, а функция меньшего размера не понадобится, поскольку она встроена.только детерминированное использование
inline
это поддерживать Один
Правило определения.
т.е. если функция объявлена с inline
затем только ниже вещи обязательны:
.cpp
файлы), компилятор будет генерировать только 1 определение и избежать ошибки компоновщика нескольких символов. (Примечание: если тела этой функции отличаются, то это неопределенное поведение.)inline
Функция должна быть видимой / доступной во всех единицах перевода, которые ее используют. Другими словами, объявив inline
функция в .h
и определяя в кто-нибудь .cpp
файл приведет к «неопределенной ошибке компоновщика символов» для других .cpp
файлыВосприятие «А» полностью неправильно и восприятие «B» полностью право.
В стандарте есть несколько цитат по этому вопросу, однако я ожидаю ответа, который логически объясняет, является ли этот вердикт верным или неверным.
Я не был уверен в вашем требовании:
Меньшие функции автоматически «встроены» оптимизатором, независимо от того, упоминается встроенный или нет …
Совершенно очевидно, что пользователь не имеет никакого контроля над функцией «встраивание» с использованием ключевого словаinline
,
Я слышал, что компиляторы могут игнорировать ваши inline
просьба, но я не думаю, что они проигнорировали это полностью.
Я просмотрел репозиторий Github для Clang и LLVM, чтобы выяснить это. (Спасибо, программное обеспечение с открытым исходным кодом!) Я узнал, что inline
ключевое слово делает сделать Clang / LLVM более вероятным для включения функции.
В поисках слова inline
в хранилище Clang приводит к спецификатору токена kw_inline
, Похоже, что Clang использует умную систему на основе макросов для создания лексера и других функций, связанных с ключевыми словами, так что if (tokenString == "inline") return kw_inline
быть найденным. Но Здесь, в ParseDecl.cpp, Мы видим, что kw_inline
приводит к звонку DeclSpec::setFunctionSpecInline()
,
case tok::kw_inline:
isInvalid = DS.setFunctionSpecInline(Loc, PrevSpec, DiagID);
break;
Внутри этой функции, мы устанавливаем немного и выдаем предупреждение, если это дубликат inline
:
if (FS_inline_specified) {
DiagID = diag::warn_duplicate_declspec;
PrevSpec = "inline";
return true;
}
FS_inline_specified = true;
FS_inlineLoc = Loc;
return false;
В поисках FS_inline_specified
в другом месте, мы видим, что это бит в поле, и он используется в функции получения, isInlineSpecified()
:
bool isInlineSpecified() const {
return FS_inline_specified | FS_forceinline_specified;
}
Поиск сайтов вызовов isInlineSpecified()
, мы нашли кодеген, где мы преобразуем дерево разбора C ++ в промежуточное представление LLVM:
if (!CGM.getCodeGenOpts().NoInline) {
for (auto RI : FD->redecls())
if (RI->isInlineSpecified()) {
Fn->addFnAttr(llvm::Attribute::InlineHint);
break;
}
} else if (!FD->hasAttr<AlwaysInlineAttr>())
Fn->addFnAttr(llvm::Attribute::NoInline);
Мы закончили со стадией разбора C ++. Теперь наш inline
спецификатор преобразуется в атрибут не зависящего от языка LLVM Function
объект. Мы переключаемся с Clang на хранилище LLVM.
В поисках llvm::Attribute::InlineHint
дает метод Inliner::getInlineThreshold(CallSite CS)
(со страшной на вид if
блок):
// Listen to the inlinehint attribute when it would increase the threshold
// and the caller does not need to minimize its size.
Function *Callee = CS.getCalledFunction();
bool InlineHint = Callee && !Callee->isDeclaration() &&
Callee->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
Attribute::InlineHint);
if (InlineHint && HintThreshold > thres
&& !Caller->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
Attribute::MinSize))
thres = HintThreshold;
Таким образом, у нас уже есть базовый порог наклона от уровня оптимизации и других факторов, но если он ниже глобального HintThreshold
Мы поднимаем это. (HintThreshold устанавливается из командной строки.)
getInlineThreshold()
кажется, имеет только сайт одного звонка, член SimpleInliner
:
InlineCost getInlineCost(CallSite CS) override {
return ICA->getInlineCost(CS, getInlineThreshold(CS));
}
Он вызывает виртуальный метод, также называемый getInlineCost
на его член указатель на экземпляр InlineCostAnalysis
,
В поисках ::getInlineCost()
чтобы найти версии, которые являются членами класса, мы находим одну, которая является членом AlwaysInline
— которая является нестандартной, но широко поддерживаемой функцией компилятора — и другая, которая является членом InlineCostAnalysis
, Он использует его Threshold
параметр Вот:
CallAnalyzer CA(Callee->getDataLayout(), *TTI, AT, *Callee, Threshold);
bool ShouldInline = CA.analyzeCall(CS);
CallAnalyzer::analyzeCall()
более 200 строк и делает настоящую мрачную работу по определению, является ли функция встроенной. Он весит много факторов, но, читая метод, мы видим, что все его вычисления либо манипулируют Threshold
или Cost
, И в конце:
return Cost < Threshold;
Но возвращаемое значение по имени ShouldInline
действительно неправильно. На самом деле основная цель analyzeCall()
это установить Cost
а также Threshold
переменные-члены на CallAnalyzer
объект. Возвращаемое значение указывает только случай, когда какой-то другой фактор переопределил анализ затрат по сравнению с порогом, как мы видим здесь:
// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
return InlineCost::getAlways();
В противном случае мы возвращаем объект, который хранит Cost
а также Threshold
,
return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
Таким образом, мы не возвращаем решение «да» или «нет» в большинстве случаев. Поиски продолжаются! Где это возвращаемое значение getInlineCost()
используемый?
Это найдено в bool Inliner::shouldInline(CallSite CS)
, Еще одна большая функция. Это вызывает getInlineCost()
в самом начале.
Оказывается, что getInlineCost
анализирует свойственный стоимость встраивания функции — ее сигнатура аргумента, длина кода, рекурсия, ветвление, связь и т. д. — и некоторая совокупная информация о каждый Поместите функцию используется. С другой стороны, shouldInline()
объединяет эту информацию с большим количеством данных о конкретный место, где используется функция.
На протяжении всего метода есть вызовы InlineCost::costDelta()
— который будет использовать InlineCost
s Threshold
значение, рассчитанное по analyzeCall()
, Наконец, мы возвращаем bool
, Решение принято. В Inliner::runOnSCC()
:
if (!shouldInline(CS)) {
emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
Twine(Callee->getName() +
" will not be inlined into " +
Caller->getName()));
continue;
}
// Attempt to inline the function.
if (!InlineCallIfPossible(CS, InlineInfo, InlinedArrayAllocas,
InlineHistoryID, InsertLifetime, DL)) {
emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
Twine(Callee->getName() +
" will not be inlined into " +
Caller->getName()));
continue;
}
++NumInlined;
InlineCallIfPossible()
делает встраивание на основе shouldInline()
решение.
Итак Threshold
был затронут inline
ключевое слово, и используется в конце, чтобы решить, следует ли встроить.
Следовательно, ваше Восприятие B частично неверно, потому что, по крайней мере, один крупный компилятор меняет свое поведение оптимизации на основе inline
ключевое слово.
Тем не менее, мы также можем видеть, что inline
это только намек, и другие факторы могут перевесить его.
Оба верны.
Использование inline
может или не может повлиять на решение компилятора встроить какой-либо конкретный вызов функции. Таким образом, A верен — он действует как необязательный запрос, который вызывает функцию, которая должна быть встроена, которую компилятор может игнорировать.
Семантический эффект inline
состоит в том, чтобы ослабить ограничения правила единого определения, чтобы разрешить идентичные определения в нескольких единицах перевода, как описано в B. Для многих компиляторов это необходимо, чтобы разрешить встраивание вызовов функций — определение должно быть доступно в этой точке, и компиляторы требуется обрабатывать только одну единицу перевода за раз.