"inline" ключевое слово vs "inlining" concept

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

При чтении ниже разделите ключевое слово inline и концепцию "inlining".

Вот мой прием:

Концепция вставки

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

Ключевое слово inline

Восприятие A

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

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

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

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

Восприятие B

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

Единственное детерминированное использование inline заключается в поддержании Единого Правило определения.

то есть. если функция объявлена ​​с помощью inline, то только ниже утверждается:

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

Вердикт

Восприятие "А" совершенно неверно, и восприятие "В" совершенно правильно.

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

Ответ 1

Я не был уверен в ваших претензиях:

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

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

Я просмотрел репозиторий Github для Clang и LLVM, чтобы узнать. (Спасибо, программное обеспечение с открытым исходным кодом!) Я узнал, что ключевое слово inline делает Clang/LLVM более вероятным, чтобы встроить функцию.

Поиск

Поиск слова inline в репозиторий Clang приводит к указателю маркера kw_inline. Похоже, что Clang использует умную систему на основе макросов для создания lexer и других функций, связанных с ключевыми словами, поэтому можно найти прямой, как 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 в другом месте, мы видим его бит в битовом поле и он используется в функции getter, isInlineSpecified()

bool isInlineSpecified() const {
  return FS_inline_specified | FS_forceinline_specified;
}

Поиск сайтов вызовов isInlineSpecified(), мы находим codegen, где мы преобразуем дерево синтаксического анализа С++ в промежуточное представление 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);

Clang to LLVM

Мы закончили этап синтаксического анализа на С++. Теперь наш спецификатор 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. Возвращаемое значение указывает только на случай, когда какой-либо другой фактор переопределил анализ затрат-vs-порога, как мы видим здесь:

// 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 Threshold, вычисленное по analyzeCall(). Наконец, мы возвращаем a 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 является лишь подсказкой, а другие факторы могут перевесить его.

Ответ 2

Оба правильные.

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

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