& Quot; встроенный & Quot; ключевое слово против & quot; встраивание & quot; концепция

Я задаю этот основной вопрос, чтобы сделать записи прямо. Сослались этот вопрос а также его в настоящее время принятый ответ, что не убедительно. Тем не менее второй по популярности ответ дает лучшее понимание, но не идеально.

Читая ниже, различайте inline ключевое слово и «встраивание» концепция.

Вот мое взятие:

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

Восприятие А

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

Я оспариваю это по следующим причинам:

  1. Большие и рекурсивные функции не являются встроенными, и компилятор игнорирует inline ключевое слово.
  2. Меньшие функции автоматически добавляются оптимизатором независимо от inline упомянутое ключевое слово или нет.

Совершенно очевидно, что пользователь не имеет никакого контроля над встраиванием функций с использованием ключевого слова. inline,

Восприятие Б

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

только детерминированное использование inline это поддерживать Один
Правило определения
.

т.е. если функция объявлена ​​с inline затем только ниже вещи обязательны:

  1. Даже если его тело найдено в нескольких единицах перевода (например, включить этот заголовок в несколько .cpp файлы), компилятор будет генерировать только 1 определение и избежать ошибки компоновщика нескольких символов. (Примечание: если тела этой функции отличаются, то это неопределенное поведение.)
  2. Тело inline Функция должна быть видимой / доступной во всех единицах перевода, которые ее используют. Другими словами, объявив inline функция в .h и определяя в кто-нибудь .cpp файл приведет к «неопределенной ошибке компоновщика символов» для других .cpp файлы

решение суда

Восприятие «А» полностью неправильно и восприятие «B» полностью право.

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

36

Решение

Я не был уверен в вашем требовании:

Меньшие функции автоматически «встроены» оптимизатором, независимо от того, упоминается встроенный или нет …
Совершенно очевидно, что пользователь не имеет никакого контроля над функцией «встраивание» с использованием ключевого слова 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);

Лязг к LLVM

Мы закончили со стадией разбора 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() — который будет использовать InlineCosts 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 это только намек, и другие факторы могут перевесить его.

55

Другие решения

Оба верны.

Использование inline может или не может повлиять на решение компилятора встроить какой-либо конкретный вызов функции. Таким образом, A верен — он действует как необязательный запрос, который вызывает функцию, которая должна быть встроена, которую компилятор может игнорировать.

Семантический эффект inline состоит в том, чтобы ослабить ограничения правила единого определения, чтобы разрешить идентичные определения в нескольких единицах перевода, как описано в B. Для многих компиляторов это необходимо, чтобы разрешить встраивание вызовов функций — определение должно быть доступно в этой точке, и компиляторы требуется обрабатывать только одну единицу перевода за раз.

20

По вопросам рекламы [email protected]