Коли використовувати вбудовану функцію, а коли її не використовувати?


185

Я знаю, що inline - це підказка або запит до компілятора і використовується для уникнення накладних викликів функцій.

Тож на якій основі можна визначити, є функція кандидатом на вбудоване чи ні? У якому випадку слід уникати вкладки?


11
inline- це для прибульця C ++, що CFLAGSстосується прибульця Gentoo: ні, компіляція з цим -O3 -funroll-loops -finline-functionsне змусить літати ваш старий Pentium;)
Григорій Пакош

1
Причина не використовувати вбудований - це те, що деякі налагоджувачі не дозволять вам встановити точку розриву або перейти до вбудованої функції.
Роб deFriesse


5
Не слід визначати, чи повинна функція бути вбудованою чи ні. Нехай це зробить компілятор; це краще, ніж ти (і може вбудовувати функції вибірково на основі середовища кожного дзвінка).
Девід Торнлі

@DavidThornley Іноді, навіть якщо встановлений прапор O3, компілятор не вбудовує функцію, якщо визначення є у файлі cpp. Отже, правило, яке я дотримуюся, - це вкладати один вкладиш, а також ті функції без певних циклів.
talekeDskobeDa

Відповіді:


209

Уникнути вартості функціонального дзвінка - це лише половина історії.

робити:

  • використовувати inlineзамість#define
  • дуже маленькі функції - хороші кандидати для inline: швидшого коду та менших виконуваних файлів (більше шансів залишитися в кеш-коді)
  • функція невелика і викликається дуже часто

не:

  • великі функції: призводять до більш великих виконуваних файлів, що значно погіршує продуктивність, незалежно від швидшого виконання, що виникає внаслідок накладних витрат
  • вбудовані функції, які пов'язані вводу / виводу
  • функція рідко використовується
  • конструктори та деструктори: навіть коли вони порожні, компілятор генерує код для них
  • порушення бінарної сумісності при розробці бібліотек:
    • вбудувати існуючу функцію
    • змінити вбудовану функцію або зробити вбудовану функцію не вбудованою: попередня версія бібліотеки викликає стару реалізацію

розробляючи бібліотеку, щоб зробити клас розширюваним у майбутньому, слід:

  • додайте нелінійний віртуальний деструктор, навіть якщо тіло порожнє
  • зробити всі конструктори нелінійними
  • писати не вбудовані реалізації конструктора копіювання та оператора присвоєння, якщо клас не може бути скопійований за значенням

Пам'ятайте, що inlineключове слово є натяком на компілятор: компілятор може вирішити не вбудовувати функцію, і він може вирішити вбудовані функції, які не були позначені inlineв першу чергу. Я взагалі уникаю функції маркування inline(крім того, можливо, коли пишуть дуже маленькі функції).

Щодо продуктивності, розумним підходом є (як завжди) профілювання програми, а згодом inlineнабір функцій, що представляють вузьке місце.

Список літератури:


EDIT: Bjarne Stroustrup, Мова програмування на C ++:

Функція може бути визначена як inline. Наприклад:

inline int fac(int n)
{
  return (n < 2) ? 1 : n * fac(n-1);
}

Специфікатор inlineє натяком компілятору, що він повинен намагатися генерувати код для fac()вбудованого виклику, а не встановлювати код для функції один раз, а потім викликати через звичайний механізм виклику функції. Розумний компілятор може генерувати константу 720для дзвінка fac(6). Можливість взаємно рекурсивних вбудованих функцій, вбудованих функцій, які повторюються або не залежать від введення тощо, не дає можливості гарантувати, що кожен виклик inlineфункції насправді є вкладеним. Ступінь чіткості компілятора не може бути законодавчо встановлений, тому один компілятор може генерувати 720, інший 6 * fac(5), а ще один невкладений дзвінок fac(6).

Щоб зробити вбудовування можливим за відсутності незвично розумних засобів компіляції та зв’язування, визначення - а не лише декларування - функції вбудованої функції повинно бути в межах сфери (§ 9.2). inlineEspecifier не впливає на семантику функції. Зокрема, вбудована функція все ще має унікальну адресу і тому має staticзмінні (§ 7.1.2) вбудованої функції.

EDIT2: ISO-IEC 14882-1998, 7.1.2 Специфікатори функції

Оголошення функції (8.3.5, 9.3, 11.4) із inlineспецифікатором оголошує вбудовану функцію. Вбудований специфікатор вказує на реалізацію, що заміна вбудованого функції функції в точці виклику повинна віддавати перевагу звичайному механізму виклику функції. Не потрібно реалізовувати для виконання цієї заміни вбудовані в точці виклику; однак, навіть якщо ця заміна вбудованої лінії опущена, інші правила для функцій вбудованого рівня, визначені 7.1.2, все ще повинні дотримуватися.


34
inlineнабагато більше, ніж натяк на компілятор. Це змінює мовні правила щодо кількох визначень. Також статичні дані - це не чавунна причина, щоб уникнути вбудовування функції. Реалізація зобов'язана виділити один статичний об'єкт для кожної статичної функції, незалежно від того, оголошена функція inlineчи ні. Класи все ще розширюються, якщо вони мають вбудовані конструктори та віртуальні деструктори. І порожній брекет-деструктор - це одна віртуальна функція, яку іноді корисно залишити в рядку.
CB Bailey

2
Це підказка в тому сенсі, що функція не обов'язково закінчується окресленою (але англійська мова - це не моя рідна мова). Щодо статики в функціях, позначених inline, то результат полягає в тому, що функція не буде вбудована: ви платите ціну за дзвінок, а також кожен блок перекладу, який включає та викликує функцію, отримує власну копію коду та статичних змінних. Причиною не вбудовування конструкторів та деструкторів при розробці бібліотеки є двійкова сумісність із майбутніми версіями вашої бібліотеки
Gregory Pakosz

14
Неточно називати це "натяком на компілятор". Насправді нефункції inlineможна накреслити, якщо компілятор відчуває це. І inlineфункції не будуть накреслені, якщо компілятор вирішить їх не вбудовувати. Як сказав Чарльз Бейлі, це змінює мовні правила. Замість того, щоб думати про це як натяк на оптимізацію, точніше мислити це як зовсім інше поняття. inlineКлючове слово вказує компілятору дозволити кілька визначень, і більше нічого. Оптимізація "вбудовування" може застосовуватися майже до будь-якої функції, незалежно від того, позначена вона чи ні inline.
jalf

26
Просто тоді, коли Stroustrup пише, що "вбудований специфікатор є натяком на компілятор", я здивований, що я звинувачую його в цитуванні. У всякому разі, я витратив достатньо часу, роблячи все можливе, щоб підтримати цю відповідь якомога більше посилань
Григорій Пакош

2
@GregoryPakosz: Але ми не всі використовуємо inlineдля отримання вбудованої функції. Іноді ми хочемо інших переваг, таких як обхід ODR.
Гонки легкості по орбіті

57

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

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

Компілятор може вбудувати (тобто замінити виклик функції на код, який виконує цю дію цієї функції) будь-який виклик функції, який він обрав. Раніше це було так, що він "очевидно" не міг вбудувати функцію, яка не була оголошена в тому ж блоці перекладу, що і виклик, але при збільшенні використання оптимізації часу зв'язку навіть це зараз не вірно. Не менш правдивим є той факт, що позначені функції inlineможуть бути не окреслені .


У мене виникає відчуття, що це скоріше щасливий збіг, ніж навмисна особливість C ++. Ідея дуже схожа на "статичні" глобальні змінні від C. Хоча це дуже цікава відповідь. Я б хотів, щоб вони просто використовували ключове слово типу "внутрішній" для позначення внутрішньої зв'язку.
Рехно Ліндек

+1. @Rehno: Я не дуже впевнений, що ти кажеш. Що пов'язане з inlineключовим словом? А що таке щасливий збіг?
jalf

@jalf: Читаючи мій коментар заднім числом, я розумію, що це досить розпливчасто і не так добре продумано. Визначення однієї і тієї ж функції в декількох файлах призводить до помилки лінкера, яку можна усунути, оголосивши функцію "статичною". Однак "вбудований" дозволяє вам робити те саме, що має тонкі відмінності, що вони насправді не мають внутрішнього зв'язку, як "статичний". Я підозрюю, що це насправді більше збіг обставин, оскільки реалізатори / дизайнери мови зрозуміли, що їм потрібно буде робити щось особливе з функціями, оголошеними у файлах заголовків, і які переносяться на 'inline'.
Rehno Lindeque

4
Не впевнений, чому ваш коментар набрав стільки голосів, оскільки ефективність є домінуючою причиною для використання в режимі Inline.
gast128

10

Розповідати компілятору про вбудовану функцію - це оптимізація, і найважливіше правило оптимізації полягає в тому, що передчасна оптимізація є коренем усього зла. Завжди пишіть чіткий код (використовуючи ефективні алгоритми), потім профіліруйте свою програму та лише оптимізуйте функції, які займають занадто багато часу.

Якщо ви виявите, що певна функція є дуже короткою і простою, і її викликають десятки тисяч разів у тісному внутрішньому циклі, це може бути хорошим кандидатом.

Можливо, ви здивуєтеся - багато компіляторів C ++ автоматично вбудують для вас невеликі функції - і вони також можуть проігнорувати ваш запит на вбудовану лінію.


Дійсно, у мене є підозри, що деякі компілятори дуже підступно ігнорують "inline" повністю і відповідають лише на "__inline" або "__force_inline". Я вважаю, що це стримує зловживання!
Рено Ліндек

Не зазвичай так. inline - це лише натяк, але більшість компіляторів це серйозно. Ви можете встановити компілятор для випромінювання мови складання разом з об'єктним кодом ( /FAcsу Visual Studio, -sв GCC), щоб побачити, що саме він робить. На мій досвід, обидва ці компілятори досить сильно оцінюють вбудоване ключове слово.
Crashworks

1
Це цікаво, оскільки, за моїм досвідом, ні g ++, ні VC не зважують inlineключове слово. Тобто, якщо ви бачите, що функція є вбудованою, і вилучите inlineз неї специфікатор, вона все одно стане вбудованою. Якщо у вас є конкретні приклади протилежного, будь ласка, поділіться ними!
Павло Мінаєв

4
як inlineключове слово перешкоджає "очистити код"? Ключове слово в "передчасна оптимізація" - передчасна , а не оптимізація. Говорити, що слід активно * уникати оптимізацій - це лише сміття. Суть цієї цитати полягає в тому, що вам слід уникати оптимізацій, які можуть не бути необхідними, і мати шкідливі побічні ефекти на код (наприклад, зробити його менш рентабельним). Я не бачу, як inlineключове слово зробить код менш рентабельним, або як це може бути шкідливо додати його до функції.
jalf

3
jalf, іноді вбудована функція зробить ваш код повільнішим, а не швидшим. Один із прикладів - коли функція викликається з декількох різних місць у вашому коді; якщо функція не вбудована, вона може все-таки знаходитися в кеші інструкцій, коли вона викликається з іншого місця, і передбачувач гілки вже може бути розігрітий. Є деякі зразки, які завжди підвищують ефективність, тому використовувати їх ніколи не зашкодить. Вкладиш - не одна з них. Зазвичай це взагалі не впливає на продуктивність, іноді допомагає, а іноді болить. Я стою за своєю порадою: спочатку профіль, а потім рядковий.
dmazzoni

5

Найкращий спосіб дізнатися це - профілювати свою програму та відзначати невеликі функції, які називаються багато разів і записуються через цикли процесора inline. Ключове слово тут "мале" - коли накладні витрати виклику функцій є незначними порівняно з часом, проведеним у функції, їх безглуздо вбудовувати.

Інше використання, яке я пропоную, - це якщо у вас є невеликі функції, які викликають критичний код продуктивності досить часто, щоб зробити кеш-пропуском відповідним, ви, ймовірно, повинні також вбудувати їх. Знову ж таки, це те, що повинен мати тобі промовець.


4

Передчасна оптимізація - корінь усього зла!

Як правило, я вказую лише "геттери" та "сетери". Після того, як код працює і стабільний, профілювання може показати, які функції можуть отримати користь від вбудовування.

З іншого боку, більшість сучасних компіляторів мають досить хороші алгоритми оптимізації, і вони будуть вказувати те, що ви повинні мати для вас.

Повторне відновлення - пишіть вбудовані функції з одним вкладишем і пізніше переживайте за інших.


2

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

ви можете прочитати більше про рядки у файлі c ++


1

Я часто використовую вбудовані функції не як оптимізацію, а щоб зробити код читабельнішим. Іноді сам код коротший і простіший для розуміння, ніж коментарі, описові назви тощо. Наприклад:

void IncreaseCount() { freeInstancesCnt++; }

Читач одразу знає повну семантику коду.


0

Я, як правило, дотримуюся правила великого пальця, де я виконую функцію з 3-4 простими висловлюваннями як вбудовані. Але добре пам’ятати, що це лише натяк на компілятора. Остаточний виклик, щоб зробити його вбудованим чи ні, приймається лише компілятором. Якщо таких заяв більше, я не оголошу вбудований текст, оскільки з дурним компілятором це може призвести до роздуття коду.


0

Найкращим способом було б вивчити та порівняти згенеровані інструкції для вбудованих та не накреслених. Однак опускати це завжди безпечно inline. Використання inlineможе призвести до проблем, яких ви не хочете.


0

Приймаючи рішення про те, чи потрібно використовувати вбудований, я зазвичай маю на увазі таке уявлення: На сучасних машинах затримка пам’яті може бути більшим місцем, ніж сирі розрахунки. Функції вбудовування, які називаються часто, як відомо, збільшують розмір виконуваних файлів. Крім того, така функція може бути збережена в кеш-коді центрального процесора, що зменшить кількість пропусків кешу, коли потрібно отримати доступ до цього коду.

Отже, ви повинні вирішити для себе: чи збільшує чи зменшує розмір створюваного машинного коду розмір? Наскільки ймовірно, що виклик функції призведе до пропуску кешу? Якщо він перецьовується у всьому коді, то я б сказав, що ймовірність висока. Якщо вона обмежена однією тісною петлею, то ймовірність, мабуть, мала.

Я зазвичай використовую вкладки у випадках, які я перелічу нижче. Однак, коли ви щиро заклопотані результатами, профайлінг має важливе значення. Крім того, ви можете перевірити, чи компілятор насправді приймає підказку.

  • Короткі підпрограми, які називаються в тісній петлі.
  • Дуже основні функції аксесуарів (get / set) та обгортки.
  • Код шаблону у файлах заголовка, на жаль, автоматично отримує підказку вбудованого.
  • Короткий код, який використовується як макрос. (Наприклад, min () / max ())
  • Короткі математичні процедури.

0

Також вбудований метод має серйозні побічні ефекти при підтримці великих проектів. Коли змінений вбудований код, усі файли, які його використовують, буде відновлено автоматично компілятором (це хороший компілятор). Це може витратити багато часу на розвиток.

Коли inlineметод переноситься у вихідний файл і більше не вказується, весь проект повинен бути перебудований (принаймні, це був мій досвід). А також коли методи перетворюються на вбудовані.


1
Це вже інше питання. Ви отримуєте проблему відновлення коду, розміщеного у файлі заголовка. Позначено inlineчи ні, не має значення (окрім як без inlineключового слова, ви отримаєте помилки в посиланнях, але inlineключове слово не є проблемою, що спричиняє надмірні перебудови.
jalf

Однак зміна вбудованого методу призведе до надмірного нарощування порівняно зі зміною не вбудованого методу у файлі shource.
Томас Меттьюз

0

Використовувати класифікатор вбудованої функції слід лише тоді, коли код функції невеликий. Якщо функцій більше, то вам слід віддати перевагу нормальним функціям, оскільки економія в просторі пам’яті варто порівняно невеликої жертви у швидкості виконання.


0

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


-2

Я прочитав кілька відповідей і бачу, що там відсутні деякі речі.

Правило, яке я використовую, - це не використовувати вбудований, якщо тільки я не хочу, щоб воно було вбудованим. Виглядає дурно, тепер пояснення.

Компілятори досить розумні, і короткі функції завжди роблять вбудованими. І ніколи не виконує функцію тривалості як вбудований, якщо програміст не сказав цього робити.

Я знаю, що inline - це підказка або запит до компілятора

Насправді inlineце замовлення для компілятора, він не має вибору і після inlineключового слова робить весь код вбудованим. Таким чином, ви ніколи не можете використовувати inlineключове слово, і компілятор створить найкоротший код.

То коли використовувати inline?

Щоб використовувати, якщо ви хочете мати якийсь код вбудований. Я знаю лише один приклад, тому що використовую його лише в одній ситуації. Це аутентифікація користувача.

Наприклад, у мене є така функція:

inline bool ValidUser(const std::string& username, const std::string& password)
{
    //here it is quite long function
}

Незалежно від того, наскільки великою є ця функція, я хочу мати її як вбудовану, тому що це робить моє програмне забезпечення важче зламати.


2
inline все ще натяк. Компілятор може не вдатися до рядка, якщо він вважає, що ваша функція занадто роздута.
ItPete

Один каже, що рядковий - це наказ ... інший каже, що це натяк. Чи хтось обґрунтує його твердження, щоб ми могли визначити, який із них правдивий?

@ user2918461 Я підтримую твердження в рядку - це лише натяк. Це було підтримано багатьма веб-сайтами та книгами
WARhead
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.