Переваги вбудованих функцій в C ++?


254

Які переваги / недоліки використання вбудованих функцій в C ++? Я бачу, що це лише збільшує продуктивність для коду, який видає компілятор, але з оптимізованими сьогодні компіляторами, швидкими процесорами, величезною пам'яттю тощо (не так, як у 1980 р. <Де пам'яті було дефіцитно і все повинно вміщуватися в 100 КБ пам'яті), що переваги чи справді вони мають сьогодні?


48
Це одне з тих питань, коли загальновідомі знання неправильні. Усі відповіли стандартною відповіддю Comp Sci. (Вбудований додаток заощаджує витрати на виклик функції, але збільшує розмір коду). Сміття. Він забезпечує простий механізм для компілятора застосувати більше ОПТИМІЗАЦІЙ.
Мартін Йорк

37
Це одна з таких відповідей, котрі представляють коментарі. Якщо вам не подобається жоден із відповідей, які були опубліковані, опублікуйте власну відповідь і подивіться, як це відбувається.
Дейв Ван ден Ейнде

10
Основа цього питання хибна. Вбудовані функції C ++ мають мало спільного з компіляторами, які містяться під час компіляції. Прикро, що inlineце ключове слово c ++, а інлінінг - це техніка оптимізації компілятора. Дивіться це питання " коли я повинен написати ключове слово inlineдля функції / методу " для правильної відповіді.
deft_code

3
@JoseVega Ваше посилання заблукало - поточне посилання є exforsys.com/tutorials/c-plus-plus/inline-functions.html

Відповіді:


143

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

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

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

Я можу побачити таку ситуацію, яка робить помітну різницю:

inline int aplusb_pow2(int a, int b) {
  return (a + b)*(a + b) ;
}

for(int a = 0; a < 900000; ++a)
    for(int b = 0; b < 900000; ++b)
        aplusb_pow2(a, b);

26
Як я підозрював, що вкладка не має ніякого значення для вищезазначеного. Укладено з gcc 4.01. Версія 1 змушена використовувати вкладиші: 48.318u 1.042s 5: 51.39 99.4% 0 + 0k 0 + 0io 0pf + 0w Версія 2 змушена не вкладати 348.311u 1.019s 5: 52.31 99.1% 0 + 0k 0 + 0io 0pf + 0w Це хорошим прикладом були загальновідомі помилки.
Мартін Йорк,

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

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

4
Причиною відсутності різниці може бути те, що компілятор може вказувати власне бажання; або що код невеликий, щоб не було проблем з попереднім вибором коду.
einpoklum

3
@einpoklum Компілятор, можливо, навіть оптимізував весь цикл через це.
noɥʇʎԀʎzɐɹƆ

197

Переваги

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

Недоліки

  • Це може збільшити ваш код (тобто якщо ви використовуєте вбудований для нетривіальних функцій). Таким чином, це може спровокувати пейджинг та перемогти оптимізацію компілятора.
  • Це трохи розбиває вашу інкапсуляцію, тому що це піддається внутрішній обробці вашого об'єкта (але тоді, кожен "приватний" член теж). Це означає, що ви не повинні використовувати вбудовування в шаблоні PImpl.
  • Це трохи розбиває вашу інкапсуляцію 2: Вбудована C ++ вирішується під час компіляції. Це означає, що якщо ви зміните код вбудованої функції, вам потрібно буде перекомпілювати весь код, використовуючи його, щоб бути впевненим, що він буде оновлений (з тієї ж причини, я уникаю значень за замовчуванням для параметрів функції)
  • При використанні в заголовку він збільшує файл заголовка і, таким чином, розбавляє цікаву інформацію (наприклад, список методів класу) кодом, про який користувач не піклується (це є причиною того, що я оголошую вбудовані функції всередині клас, але визначимо його в заголовку після тіла класу, а ніколи в тілі класу).

Магія магії

  • Компілятор може або не може вбудовувати функції, які ви позначили як вбудовані; він також може вирішити вбудовувати функції, не позначені як вбудовані під час компіляції чи зв’язування.
  • Inline працює як контрольована компілятором копія / паста, що сильно відрізняється від макросу попереднього процесора: макрос буде насильно вкладений, забруднить усі простори імен та код, його не буде легко відладжувати, і це буде зроблено навіть якщо компілятор визнав би це неефективним.
  • Кожен метод класу, визначений всередині самого класу, вважається "вбудованим" (навіть якщо компілятор все-таки може вирішити не вбудовувати його
  • Віртуальні методи не повинні бути незрозумілими. Однак іноді, коли компілятор може точно знати тип об'єкта (тобто об’єкт був оголошений і побудований всередині одного і того ж органу функції), навіть віртуальна функція буде вписана, оскільки компілятор точно знає тип об'єкта.
  • Методи / функції шаблонів не завжди вбудовані (їх наявність у заголовку не зробить їх автоматично вбудованими).
  • Наступним кроком після "inline" є метапрограмування шаблонів. Тобто, "вставляючи" ваш код під час компіляції, іноді компілятор може вивести кінцевий результат функції ... Тож складний алгоритм іноді може бути зведений до свого роду return 42 ;оператора. Це для мене надзвичайна думка . У реальному житті це трапляється рідко, це збільшує час компіляції, не розмиває ваш код і зробить ваш код швидшим. Але як грааль, не намагайтеся застосовувати його скрізь, оскільки більшість обробок не можна вирішити таким чином ... Все-таки це круто все одно ...
    :-p

Ви сказали, що це трохи порушує вашу інкапсуляцію. Чи поясніть, будь ласка, на прикладі?
Деструктор

6
@PravasiMeet: Це C ++. Скажімо, ви доставляєте бібліотеку DLL / спільну бібліотеку клієнту, який компілює її. Вбудована функція foo, використовуючи змінну X члена та виконуючи роботу Y, буде введена в код клієнта. Скажімо, потрібно поставити оновлену версію DLL, де ви змінили змінну члена на Z, а також додайте роботу YY на додаток до роботи Y. Клієнт просто копіює DLL у свій проект, і BOOM, тому що код foo у їхньому двійковому файлі не є оновлений код, який ви написали ... Незважаючи на те, що клієнт не має легального доступу до вашого приватного коду, вкладення робить його досить "загальнодоступним".
paercebal

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

@KonradRudolph В n4594 я бачу: 3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template. В 5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template. І 7.1.2/2: the use of inline keyword is to declare an inline functionтам, де є пропозиція вбудувати функціональне тіло в точці виклику. Отже, я роблю висновок, що навіть якщо вони можуть вести себе однаково, вбудовані функції та шаблони функцій все ще є окремими, ортогональними поняттями, які можна змішати (тобто шаблон вбудованої функції)
paercebal

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

42

В архаїчних C і C ++, inlineце як register: пропозиція (не що інше, як пропозиція) компілятору про можливу оптимізацію.

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

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

Функції учасників, визначені в класі, за замовчуванням є "вбудованими", як і шаблонні функції (на відміну від глобальних функцій).

//fileA.h
inline void afunc()
{ std::cout << "this is afunc" << std::endl; }

//file1.cpp
#include "fileA.h"
void acall()
{ afunc(); }

//main.cpp
#include "fileA.h"
void acall();

int main()
{ 
   afunc(); 
   acall();
}

//output
this is afunc
this is afunc

Зверніть увагу на включення fileA.h у два .cpp-файли, в результаті чого з'являються два екземпляри afunc(). Лінкер відкине один з них. Якщо не inlineвказано, лінкер подасть скаргу.


16

Вкладиш - це пропозиція для компілятора, яку він може ігнорувати. Він ідеально підходить для невеликих шматочків коду.

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

Він також допомагає процесорам з конвеєрним рухом, оскільки їм не доведеться перезавантажувати конвеєр новими інструкціями, викликаними викликом.

Єдиним недоліком є ​​можливий збільшений розмір бінарних файлів, але, поки функцій мало, це не матиме великого значення.

Я схильний залишати такі рішення сьогодні компіляторам (ну, розумним - все одно). Люди, які їх писали, як правило, мають набагато більш детальні знання про основні архітектури.


12

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

Переваги: ​​-

  1. Для цього не потрібна функція виклику накладних витрат.

  2. Він також зберігає накладні витрати змінних push / pop на стеку, при цьому функція викликає.

  3. Це також зберігає накладні витрати на зворотний дзвінок від функції.

  4. Це збільшує локальність посилання за допомогою кешу інструкцій.

  5. Після вбудовування компілятор також може застосувати внутрішньопроцедуальну оптимізацію, якщо зазначено. Це найважливіший; таким чином компілятор тепер може зосередитись на усуненні мертвого коду, може надати більше навантаження на прогнозування гілок, усунення індукційних змінних тощо.

Щоб дізнатися більше про нього, ви можете перейти за цим посиланням http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html


4
1) Це пропозиція, а не інструкція 2) Це може спричинити більше пропусків кешу через збільшення розміру коду, якщо часто використовується впорядкована функція
Flexo

3
Чи є посилання в кінці вашим особистим блогом? Якщо це так, ви повинні оголосити це як таке, інакше це виглядає як спам.
Flexo

6

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

Це може бути критично важливим для завантаження бібліотеки під час виконання. Він також може потрапляти в бібліотеки, що знають бінарні файли. У таких випадках не використовуйте inline.


@Johnsyweb: уважно прочитай мою відповідь. Те, що ви сказали, є істинним, коли ви будуєте виконуваний файл. Але компілятор не може просто ігнорувати, inlineбудуючи спільну бібліотеку!
doc

4

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


Багато компіляторів також цього не робитимуть, наприклад, MSVC цього не зробить, якщо ви цього не скажете
паульм

4

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


3

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

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

Коли вбудувати та / або визначити макрос для примусового включення? - Тільки тоді, коли у вас є продемонстроване та необхідне доведене збільшення швидкості для критичного розділу коду, який, як відомо, впливає на загальну продуктивність програми.


… Якщо ви дбаєте про простір, скажіть компілятору оптимізувати простір - кажучи компілятору оптимізувати швидкість, це може призвести до менших двійкових файлів із C ++ та C. Аналогічно, кажучи компілятору оптимізувати для простору, це може привести до швидшого виконання. Так, ці функції не завжди працюють як рекламовані. люди мають змогу краще зрозуміти деякі аспекти своєї програми, ніж узагальнені інтерпретації компілятора (які також повинні бути зручними в користуванні). втручання людини не повинно бути поганим.
Justin

3

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


1

Випали в тій же проблемі з вбудованими функціями в такі бібліотеки. Здається, що вбудовані функції не збираються в бібліотеку. в результаті лінкер видає помилку "невизначеної посилання", якщо виконуваний файл хоче використовувати вбудовану функцію бібліотеки. (трапилось зі мною компіляція джерела Qt з gcc 4.5.


1

Чому б не зробити всі функції вбудованими за замовчуванням? Тому що це інженерна торгівля. Існує щонайменше два типи "оптимізації": прискорення програми та зменшення розміру (слід пам'яті) програми. Вбудовування, як правило, прискорює справи. Це позбавляється від набору функцій виклику, уникаючи натискання та витягування параметрів зі стека. Однак це також збільшує слід пам'яті програми, оскільки кожен виклик функції тепер повинен бути замінений на повний код функції. Щоб зробити це ще більш складним, пам’ятайте, що процесор зберігає часто використовувані фрагменти пам'яті в кеші на процесорі для надшвидкого доступу. Якщо ви зробите зображення пам'яті програми достатньо великим, програма не зможе ефективно використовувати кеш, і в гіршому випадку вбудова може фактично уповільнити програму.


1

Наш професор інформатики закликав нас ніколи не використовувати inline в програмі c ++. На запитання чому, він люб’язно пояснив нам, що сучасні компілятори повинні визначати, коли використовувати Inline автоматично.

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


5
Твій професор, на жаль, абсолютно помиляється. inlineу C ++ є два дуже чітких значення - лише одне з них пов'язане з оптимізацією, і ваш професор щодо цього є правильним. Однак, другий зміст inlineчасто необхідний для задоволення Правила одного визначення .
Конрад Рудольф

-1

Висновок з іншої дискусії тут:

Чи є недоліки з вбудованими функціями?

Мабуть, немає нічого поганого у використанні вбудованих функцій.

Але варто відзначити наступні моменти!

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

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

  • Є кілька ситуацій, коли вбудована функція може не працювати:

    • Для функції, що повертає значення; якщо заява про повернення існує
    • Для функції, яка не повертає жодних значень; якщо оператор циклу, комутатора або goto існує.
    • Якщо функція є рекурсивною. -Джерело
  • __inlineКлючове слово викликає функцію , яка буде вбудовується тільки при вказівці параметра оптимізувати. Якщо задано оптимізацію, то чи __inlineбуде дотримано чи ні , залежить від параметра опції вбудованого оптимізатора. За замовчуванням опція вбудованої дії діє при кожному запуску оптимізатора. Якщо ви задаєте оптимізацію, ви також повинні вказати параметр noinline, якщо ви хочете, щоб __inlineключове слово було ігноровано. -Джерело


3
Було б правдою, якби inline була командою, а не натяком на компілятор. Компілятор насправді вирішує, що вбудувати.
Мартін Йорк,

1
@LokiAstari Я знаю, що inline - це запит до компілятора. Мій аргумент: Якщо його натяк на компілятора, ми повинні залишити його компілятору, щоб вирішити, що найкраще. Навіщо використовувати Inline будь-яким способом, навіть якщо ви використовуєте inline, це все-таки компілятор, який прийме остаточне рішення. Мені теж здається, що мій Microsoft представив _forceinline.
Крішна Оза

@krish_oza: Мій коментар тут. Є про цю відповідь. Відповідь тут абсолютно неправильна. Оскільки компілятор не враховує inlineключове слово, щоб визначити, чи слід вбудовувати код, всі пункти, зазначені вище, помилкові. Це було б істинно, якби компілятор використовував ключове слово для вбудовування (воно використовується лише для визначення маркування декількох визначень для цілей посилання).
Мартін Йорк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.