Kiedy używać funkcji inline, a kiedy jej nie używać?


185

Wiem, że inline jest wskazówką lub żądaniem kompilatora i służy do unikania narzutów wywołania funkcji.

Na jakiej podstawie można ustalić, czy funkcja jest kandydatem do inklinacji, czy nie? W którym przypadku należy unikać wkładania?


11
inlinejest dla początkującego C ++, co CFLAGSjest dla początkującego w Gentoo: nie, kompilacja -O3 -funroll-loops -finline-functionsnie sprawi, że twój stary Pentium poleci;)
Gregory Pakosz

1
Powodem, dla którego nie należy używać funkcji wstawiania, jest to, że niektóre debugery nie pozwalają na ustawienie punktu przerwania lub przejścia do funkcji wstawionej.
Rob deFriesse,


5
Nie należy określać, czy funkcja ma być wstawiana, czy nie. Niech kompilator to zrobi; jest w tym lepszy niż ty (i może wstawiać funkcje selektywnie w zależności od środowiska każdego połączenia).
David Thornley,

@DavidThornley Czasami, nawet z ustawioną flagą O3, kompilator nie wstawia funkcji, jeśli definicja jest w pliku cpp. Tak więc podstawową zasadą, którą stosuję, jest wstawienie jednej linijki, a także tych funkcji bez żadnych pętli.
talekeDskobeDa

Odpowiedzi:


209

Uniknięcie kosztów wywołania funkcji to tylko połowa sukcesu.

robić:

  • użyj inlinezamiast#define
  • bardzo małe funkcje są dobrymi kandydatami na inline: szybszy kod i mniejsze pliki wykonywalne (większe szanse na pozostanie w pamięci podręcznej kodu)
  • funkcja jest niewielka i wywoływana bardzo często

nie:

  • duże funkcje: prowadzi do większych plików wykonywalnych, co znacznie obniża wydajność bez względu na szybsze wykonanie wynikające z narzutu wywołania
  • funkcje wbudowane, które są powiązane we / wy
  • funkcja jest rzadko używana
  • konstruktory i destruktory: nawet gdy jest pusty, kompilator generuje dla nich kod
  • łamanie kompatybilności binarnej podczas tworzenia bibliotek:
    • wstaw istniejącą funkcję
    • zmienić funkcję wbudowaną lub uczynić funkcję wbudowaną inną niż wbudowana: poprzednia wersja biblioteki wywołuje starą implementację

opracowując bibliotekę, aby uczynić klasę rozszerzalną w przyszłości, powinieneś:

  • dodaj nieliniowy wirtualny destruktor, nawet jeśli ciało jest puste
  • uczyń wszystkich konstruktorów nieliniowymi
  • napisz nieliniowe implementacje konstruktora kopiowania i operatora przypisania, chyba że klasy nie można skopiować za pomocą wartości

Pamiętaj, że inlinesłowo kluczowe jest wskazówką dla kompilatora: kompilator może zdecydować, że nie wstawi funkcji, i może zdecydować o wstawieniu funkcji, które nie zostały oznaczone inlinew pierwszej kolejności. Generalnie unikam funkcji znakowaniainline (może poza pisaniem bardzo małych funkcji).

Jeśli chodzi o wydajność, rozsądnym podejściem jest (jak zawsze) profilowanie aplikacji, a następnie inlinezestaw funkcji reprezentujących wąskie gardło.

Bibliografia:


EDYCJA: Bjarne Stroustrup, język programowania C ++:

Można zdefiniować funkcję inline. Na przykład:

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

Specyfikator inlinejest wskazówką dla kompilatora, że ​​powinien on próbować wygenerować kod dla wywołania fac()inline, a nie ustanowić kod dla funkcji raz, a następnie wywołać za pomocą zwykłego mechanizmu wywoływania funkcji. Sprytny kompilator może wygenerować stałą 720dla połączenia fac(6). Możliwość wzajemnie rekurencyjnych funkcji inline, funkcji inline, które powtarzają się lub nie zależą od danych wejściowych itp., Uniemożliwia zagwarantowanie, że każde wywołanie inlinefunkcji jest wstawiane. Stopień sprytności kompilatora nie może być regulowany, więc jeden kompilator może wygenerować 720, inny 6 * fac(5), a jeszcze inny niewymagane wywołaniefac(6) .

Aby inlinizacja była możliwa przy braku niezwykle sprytnych narzędzi do kompilacji i łączenia, należy uwzględnić definicję - a nie tylko deklarację - funkcji wbudowanej (§9.2). inlineEspecifier nie wpływa semantyki funkcji. W szczególności funkcja wbudowana nadal ma unikalny adres, podobnie jak staticzmienne (§7.1.2) funkcji wbudowanej.

EDIT2: ISO-IEC 14882-1998, 7.1.2 Specyfikatory funkcji

Deklaracja funkcji (8.3.5, 9.3, 11.4) ze inlinespecyfikatorem deklaruje funkcję wbudowaną. Specyfikator wbudowany wskazuje implementacji, że podstawienie wbudowane funkcji w punkcie wywołania powinno być preferowane zamiast zwykłego mechanizmu wywoływania funkcji. Implementacja nie jest wymagana do wykonania tego wbudowanego podstawienia w punkcie wywołania; jednak nawet jeśli to wbudowane podstawienie zostanie pominięte, pozostałe zasady dla funkcji wbudowanych określone w 7.1.2 powinny być nadal przestrzegane.


34
inlinejest czymś więcej niż wskazówką dla kompilatora. Zmienia reguły językowe dotyczące wielu definicji. Ponadto posiadanie danych statycznych nie jest żeliwnym powodem, aby unikać wstawiania funkcji. Implementacja jest zobowiązana do przydzielenia pojedynczego obiektu statycznego dla każdej funkcji statycznej, niezależnie od tego, czy funkcja jest zadeklarowana, inlineczy nie. Klasy są nadal rozszerzalne, jeśli mają wbudowane konstruktory i wirtualne destruktory. A pusty nawias klamrowy to jedna wirtualna funkcja, którą czasem warto pozostawić w linii.
CB Bailey,

2
Jest to wskazówka w tym sensie, że funkcja niekoniecznie kończy się na inline (ale angielski nie jest moim językiem ojczystym). Jeśli chodzi o statykę w zaznaczonych funkcjach inline, wynik jest taki, że funkcja nie jest wstawiana: płacisz cenę za wywołanie, a także każda jednostka tłumacząca, która zawiera i wywołuje funkcję, otrzymuje własną kopię kodu i zmiennych statycznych. Przyczyną niestawiania konstruktorów i destruktorów podczas tworzenia biblioteki jest binarna kompatybilność z przyszłymi wersjami twojej biblioteki
Gregory Pakosz

14
Nazywanie go „wskazówką dla kompilatora” jest niedokładne. W rzeczywistości inlineniefunkcje można wstawiać, jeśli kompilator ma na to ochotę. I inlinefunkcje nie będą inlined jeśli kompilator nie decyduje się je na rolkach. Jak powiedział Charles Bailey, zmienia reguły językowe. Zamiast myśleć o nim jako o wskazówce optymalizacyjnej, bardziej trafne jest myślenie o nim jako o zupełnie innej koncepcji. inlineKluczowe informuje kompilator, aby umożliwić wiele definicji i nic innego. Optymalizację „wstawiania” można zastosować do prawie każdej funkcji, niezależnie od tego, czy jest zaznaczona inline.
czerwiec

26
Po prostu, kiedy Stroustrup pisze „specyfikator wbudowany jest wskazówką dla kompilatora”, jestem zaskoczony, że jestem winien cytowania go. W każdym razie spędziłem wystarczająco dużo czasu, starając się poprzeć tę odpowiedź możliwie jak największą liczbą referencji
Gregory Pakosz

2
@GregoryPakosz: Ale nie wszyscy używamy inline, aby uzyskać wbudowane funkcje. Czasami chcemy innych korzyści, takich jak obejście ODR.
Wyścigi lekkości na orbicie

57

inline ma bardzo mało wspólnego z optymalizacją. inlinejest instrukcją dla kompilatora, aby nie generować błędu, jeśli dana funkcja występuje wielokrotnie w programie, i obietnica, że ​​definicja pojawi się w każdym używanym tłumaczeniu i wszędzie tam, gdzie się pojawi, będzie miała dokładnie tę samą definicję.

Biorąc pod uwagę powyższe zasady, inlinenadaje się do krótkich funkcji, których ciało nie musi zawierać dodatkowych zależności od tego, czego potrzebowałaby tylko deklaracja. Za każdym razem, gdy napotyka się definicję, należy ją przeanalizować, a kod dla jej treści może zostać wygenerowany, co oznacza narzut kompilatora na funkcję zdefiniowaną tylko raz w jednym pliku źródłowym.

Kompilator może inline (tj zastąpić wywołanie funkcji z kodu, który wykonuje, że działanie tej funkcji) każde wywołanie funkcji, które wybiera. Kiedyś było tak, że „oczywiście” nie można było wstawić funkcji, która nie została zadeklarowana w tej samej jednostce tłumaczenia co wywołanie, ale przy rosnącym wykorzystaniu optymalizacji czasu łącza nawet teraz nie jest to prawdą. Równie prawdą jest fakt, że zaznaczone funkcje inlinenie mogą być wstawiane.


Mam wrażenie, że jest to bardziej szczęśliwy zbieg okoliczności niż celowa funkcja C ++. Pomysł jest bardzo podobny do „statycznych” zmiennych globalnych z C. Jest to jednak bardzo interesująca odpowiedź. Chciałbym, żeby użyli słowa kluczowego typu „wewnętrzny”, aby wskazać powiązanie wewnętrzne.
Rehno Lindeque,

+1. @ Rehno: Nie jestem do końca pewien, co mówisz. Co powiązanie ma wspólnego ze inlinesłowem kluczowym? A jaki jest szczęśliwy zbieg okoliczności?
lipiec

@jalf: Czytając mój komentarz z perspektywy czasu, zdaję sobie sprawę, że jest to raczej niejasne i nie tak dobrze przemyślane. Zdefiniowanie tej samej funkcji w wielu plikach powoduje błąd linkera, któremu można zaradzić, deklarując funkcję „statyczną”. Jednak „inline” pozwala robić to samo z subtelnymi różnicami, że tak naprawdę nie uzyskują wewnętrznego połączenia, jak robi to „static”. Podejrzewam, że tak naprawdę jest to zbieg okoliczności, ponieważ realizatorzy / projektanci języków zdali sobie sprawę, że będą musieli zrobić coś specjalnego z funkcjami zadeklarowanymi w plikach nagłówkowych i przeniesionymi do „inline”.
Rehno Lindeque,

4
Nie jestem pewien, dlaczego Twój komentarz uzyskał tak wiele głosów, ponieważ wydajność jest dominującym powodem korzystania z funkcji inline.
gast128,

10

Mówienie kompilatorowi, aby wstawił funkcję, jest optymalizacją, a najważniejszą zasadą optymalizacji jest to, że przedwczesna optymalizacja jest źródłem wszelkiego zła. Zawsze pisz czysty kod (używając wydajnych algorytmów), następnie profiluj swój program i optymalizuj tylko funkcje, które trwają zbyt długo.

Jeśli okaże się, że dana funkcja jest bardzo krótka i prosta, a wywoływana jest dziesiątki tysięcy razy w ciasnej pętli wewnętrznej, może być dobrym kandydatem.

Możesz być zaskoczony - wiele kompilatorów C ++ automatycznie wstawi za ciebie małe funkcje - i mogą zignorować twoją prośbę o wstawienie.


Rzeczywiście, mam podejrzenia, że ​​niektóre kompilatory bardzo sprytnie ignorują „inline” całkowicie i reagują tylko na „__inline” lub „__force_inline”. Przypuszczam, że ma to na celu powstrzymanie nadużyć!
Rehno Lindeque,

Zwykle tak nie jest. inline to tylko podpowiedź, ale podpowiedź, którą większość kompilatorów traktuje poważnie. Możesz ustawić kompilator, aby emitował język asemblera wraz z kodem obiektowym ( /FAcsw Visual Studio, -sw GCC), aby zobaczyć dokładnie, co robi. Z mojego doświadczenia wynika, że ​​oba te kompilatory dość mocno ważą wbudowane słowo kluczowe.
Crashworks

1
To interesujące, ponieważ z mojego doświadczenia ani inlinesłowa kluczowego g ++, ani VC nie ważą . Oznacza to, że jeśli zobaczysz funkcję wstawioną i usuniesz inlinez niej specyfikator, nadal będzie ona wstawiana. Jeśli masz jakieś konkretne przykłady przeciwne, udostępnij je!
Pavel Minaev

4
w jaki sposób inlinesłowo kluczowe utrudnia „wyczyszczenie kodu”? Słowo kluczowe w „przedwczesnej optymalizacji” jest przedwczesne , a nie optymalizacja. Mówienie, że powinieneś aktywnie * unikać optymalizacji, to po prostu śmiecie. Chodzi o to, że powinieneś unikać optymalizacji, które mogą nie być konieczne, i mieć szkodliwe skutki uboczne dla kodu (takie jak zmniejszenie jego konserwacji). Nie widzę, w jaki sposób inlinesłowo kluczowe sprawi, że kod będzie mniej konserwowalny, ani w jaki sposób dodanie go do funkcji może być szkodliwe.
lipiec

3
jalf, czasami wstawienie funkcji spowoduje spowolnienie kodu, a nie przyspieszenie. Jednym z przykładów jest wywołanie funkcji z kilku różnych miejsc w kodzie; jeśli funkcja nie jest wbudowana, może nadal znajdować się w pamięci podręcznej instrukcji, gdy jest wywoływana z innego miejsca, a predyktor gałęzi może być już rozgrzany. Istnieją pewne wzorce, które zawsze poprawiają wydajność, więc ich używanie nigdy nie boli. Inlining nie jest jednym z nich. Zwykle nie ma to żadnego wpływu na wydajność, czasem pomaga, a czasem boli. Staję za moją radą: najpierw profil, a potem inline.
dmazzoni

5

Najlepszym sposobem, aby się tego dowiedzieć, jest profilowanie programu i oznaczanie małych funkcji, które są wywoływane wiele razy i przechodzą przez cykle procesora tak jak inline. Słowo kluczowe jest tutaj „małe” - gdy narzut wywołania funkcji jest nieznaczny w porównaniu do czasu spędzonego w funkcji, nie ma sensu wstawiać ich.

Innym zastosowaniem, które zasugerowałem, jest to, że jeśli masz małe funkcje, które są wywoływane w kodzie krytycznym pod względem wydajności wystarczająco często, aby sprawić, że pamięć podręczna nie trafi, prawdopodobnie powinieneś je również wpisać. Znów jest to coś, co profiler powinien móc ci powiedzieć.


4

Przedwczesna optymalizacja jest źródłem wszelkiego zła!

Zazwyczaj umieszczam tylko „getters” i „setters”. Gdy kod działa i jest stabilny, profilowanie może pokazać, które funkcje mogą skorzystać z wstawiania.

Z drugiej strony, większość współczesnych kompilatorów ma całkiem dobre algorytmy optymalizacyjne i określi to, co powinieneś dla siebie napisać.

Reasuming - pisz wbudowane funkcje jednowierszowe, a później martw się o inne.


2

Funkcje wbudowane mogą wbudowane poprawić wydajność kodu, eliminując potrzebę wypychania argumentów do stosu. jeśli dana funkcja znajduje się w kluczowej części kodu, powinieneś podjąć decyzję inline inline w części dotyczącej optymalizacji projektu,

możesz przeczytać więcej o wstawieniach w często zadawanych pytaniach o c ++


1

Często używam funkcji wbudowanych nie jako optymalizacji, ale aby uczynić kod bardziej czytelnym. Czasami sam kod jest krótszy i łatwiejszy do zrozumienia niż komentarze, nazwy opisowe itp. Na przykład:

void IncreaseCount() { freeInstancesCnt++; }

Czytelnik natychmiast zna pełną semantykę kodu.


0

Generalnie stosuję zasadę kciuka, w której wykonuję funkcję z 3-4 prostymi instrukcjami jako wstawionymi. Warto jednak pamiętać, że jest to tylko wskazówka dla kompilatora. Ostatnie wywołanie, aby ustawić je jako wbudowane lub nie, jest podejmowane tylko przez kompilator. Jeśli jest ich więcej niż tyle, nie będę deklarować w wierszu, ponieważ w przypadku głupiego kompilatora może to prowadzić do wzdęcia kodu.


0

Najlepszym sposobem byłoby sprawdzenie i porównanie wygenerowanych instrukcji dla wstawionych i nie wstawionych. Jednak zawsze można to bezpiecznie pominąć inline. Używanie inlinemoże prowadzić do problemów, których nie chcesz.


0

Decydując się na użycie w trybie inline, zwykle pamiętam o następującym pomyśle: Opóźnienie pamięci na nowoczesnych maszynach może być większym wąskim gardłem niż surowe obliczenia. Często wywoływane funkcje wstawiania zwiększają rozmiar pliku wykonywalnego. Ponadto taka funkcja może być przechowywana w pamięci podręcznej kodu procesora, co zmniejsza liczbę braków pamięci podręcznej, gdy potrzebny jest dostęp do tego kodu.

Dlatego musisz sam zdecydować: czy wstawianie zwiększa lub zmniejsza rozmiar generowanego kodu maszynowego? Jak prawdopodobne jest, że wywołanie funkcji spowoduje brak pamięci podręcznej? Jeśli jest on przeszyty całym kodem, powiedziałbym, że prawdopodobieństwo jest wysokie. Jeśli jest ograniczony do jednej ciasnej pętli, prawdopodobieństwo jest niskie.

Zazwyczaj używam inlinizacji w przypadkach, które wymienię poniżej. Jednak jeśli naprawdę zależy Ci na wydajności, profilowanie jest niezbędne. Ponadto możesz sprawdzić, czy kompilator rzeczywiście bierze podpowiedź.

  • Krótkie procedury, które są wywoływane w ciasnej pętli.
  • Bardzo podstawowe akcesoria (get / set) i funkcje otoki.
  • Kod szablonu w plikach nagłówkowych niestety automatycznie uzyskuje podpowiedź.
  • Krótki kod używany jak makro. (Np. Min () / max ())
  • Krótkie rutyny matematyczne.

0

Również metoda wbudowana ma poważne skutki uboczne przy utrzymywaniu dużych projektów. Po zmianie kodu wbudowanego wszystkie pliki, które go używają, zostaną automatycznie odbudowane przez kompilator (jest to dobry kompilator). Może to zmarnować dużo czasu na rozwój.

Kiedy inlinemetoda jest przenoszona do pliku źródłowego i nie jest już wstawiana, cały projekt musi zostać przebudowany (przynajmniej takie było moje doświadczenie). A także, gdy metody są konwertowane na wbudowane.


1
To inny problem. Pojawia się problem z przebudową kodu umieszczonego w pliku nagłówkowym. To, czy jest zaznaczone, inlineczy nie, nie ma znaczenia (poza tym bez inlinesłowa kluczowego inline
pojawią

Jednak zmiana metody wbudowanej spowoduje nadmierne kompilacje w porównaniu ze zmianą metody innej niż wbudowana w pliku zasobów.
Thomas Matthews,

0

Kwalifikatora funkcji wbudowanej należy używać tylko wtedy, gdy kod funkcji jest mały. Jeśli funkcje są większe, należy preferować normalne funkcje, ponieważ oszczędność miejsca w pamięci jest warta stosunkowo niewielkiego poświęcenia w szybkości wykonywania.


0

Kiedy uważasz, że Twój kod jest wystarczająco mały, aby go używać jako wbudowanego i pamiętasz, że funkcja wbudowana powiela kod i wkleja go, gdy funkcja jest wywoływana, więc może być wystarczająco dobry, aby wydłużyć czas wykonywania, ale również zwiększyć zużycie pamięci. Nie możesz używać funkcji inline, gdy używasz pętli / zmiennej statycznej / rekurencyjnej / switch / goto / funkcji wirtualnej. Wirtualne oznacza poczekanie, aż środowisko wykonawcze i wbudowane oznaczają podczas kompilacji, więc nie można ich używać jednocześnie.


-2

Przeczytałem kilka odpowiedzi i widzę, że czegoś brakuje.

Zasadą, której używam, nie jest używanie wbudowane, chyba że chcę, aby było wbudowane. Wygląda głupio, teraz wyjaśnienie.

Kompilatory są wystarczająco inteligentne, a krótkie funkcje są zawsze dostępne. I nigdy nie tworzy długiej funkcji jako wbudowanej, chyba że programista tak mówi.

Wiem, że inline jest wskazówką lub prośbą do kompilatora

W rzeczywistości inlinejest to zamówienie na kompilator, nie ma możliwości wyboru, a po inlinesłowach kluczowych cały kod jest wstawiany. Dlatego nigdy nie możesz użyć inlinesłowa kluczowego, a kompilator zaprojektuje najkrótszy kod.

Więc kiedy używać inline?

Do użycia, jeśli chcesz mieć wbudowany kod. Znam tylko jeden przykład, ponieważ używam go tylko w jednej sytuacji. To jest uwierzytelnianie użytkownika.

Na przykład mam tę funkcję:

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

Bez względu na to, jak duża jest ta funkcja, chcę mieć ją wbudowaną, ponieważ utrudnia to złamanie mojego oprogramowania.


2
Inline jest nadal wskazówką. Kompilator może nie zostać wstawiony, jeśli uzna, że ​​funkcja jest zbyt rozdęta.
It'sPete

Jeden mówi, że inline jest rozkazem ... drugi mówi, że to wskazówka Czy ktoś uzasadniłby to stwierdzenie, abyśmy mogli ustalić, które z nich jest prawdziwe?

@ user2918461 Wspieram wbudowane oświadczenie jest tylko podpowiedź. Wspierało to wiele stron internetowych i książek
WARhead
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.