Korzyści funkcji wbudowanych w C ++?


254

Jakie są zalety / wady korzystania z funkcji wbudowanych w C ++? Widzę, że zwiększa to tylko wydajność kodu generowanego przez kompilator, ale przy dzisiejszych zoptymalizowanych kompilatorach, szybkich procesorach, ogromnej pamięci itp. (Nie tak jak w 1980 r. <Gdzie pamięci było mało i wszystko musiało zmieścić się w 100 KB pamięci) co zalety, które naprawdę mają dzisiaj?


48
To jedno z tych pytań, w których powszechna wiedza jest błędna. Wszyscy odpowiedzieli standardową odpowiedzią Comp Sci. (Inlining oszczędza koszty wywołania funkcji, ale zwiększa rozmiar kodu). Śmieci. Zapewnia prosty mechanizm kompilatora do zastosowania większej OPTYMALIZACJI.
Martin York

37
To jedna z tych odpowiedzi udających komentarze. Jeśli nie podoba Ci się żadna z opublikowanych odpowiedzi, opublikuj własną odpowiedź i zobacz, jak to idzie.
Dave Van den Eynde

10
Podstawa tego pytania jest błędna. Funkcje wbudowane w C ++ mają niewiele wspólnego z wbudowanymi kompilatorami podczas kompilacji. Niestety, inlinejest to słowo kluczowe c ++, a inlining to technika optymalizacji kompilatora. Zobacz to pytanie „ kiedy powinienem napisać słowo kluczowe inlinedla funkcji / metody ”, aby uzyskać poprawną odpowiedź.
deft_code

3
@JoseVega Twój link został zniekształcony - bieżący link to exforsys.com/tutorials/c-plus-plus/inline-functions.html

Odpowiedzi:


143

Funkcje śródliniowe są szybsze, ponieważ nie trzeba popychać i wyrzucać elementów na stosie, takich jak parametry i adres zwrotny; powoduje to jednak, że twój plik binarny jest nieco większy.

Czy to robi znaczącą różnicę? Większość z nich jest niewystarczająco na nowoczesnym sprzęcie. Ale może mieć znaczenie, co wystarcza niektórym ludziom.

Oznaczenie czegoś w linii nie daje gwarancji, że będzie ono w linii. To tylko propozycja dla kompilatora. Czasami nie jest to możliwe, na przykład gdy masz funkcję wirtualną lub gdy występuje rekurencja. A czasami kompilator po prostu decyduje się go nie używać.

Widziałem taką sytuację, która ma zauważalną różnicę:

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
Jak podejrzewałem, inklinacja nie ma znaczenia dla powyższego. Kompilowany z gcc 4.01. Wersja 1 zmuszona do korzystania z wbudowania: 48,318u 1,042s 5: 51,39 99,4% 0 + 0k 0 + 0io 0pf + 0w Wersja 2 wymuszona bez wbudowania 348,311u 1,019s 5: 52,31 99,1% 0 + 0k 0 + 0io 0pf + 0w To jest dobry przykład, że powszechna wiedza jest błędna.
Martin York,

36
podczas gdy samo połączenie rzeczywiście ma znaczenie, to tylko niewielki zysk, jaki można uzyskać za pomocą wbudowanego. Główną korzyścią jest to, że kompilator widzi teraz, gdzie wskaźniki nie aliasują się, gdzie zmienne wywołującego kończą się w odbiorcy i tak dalej. Dlatego ważniejsza jest następująca optymalizacja.
Johannes Schaub - litb

31
Prawdopodobnie nie ma znaczenia w tym fragmencie, ponieważ wynik funkcji nigdy nie jest używany, ani nie ma skutków ubocznych. Widzimy wymierny wzrost wydajności wbudowania w przetwarzanie obrazu.
cokół

4
Powodem braku różnicy może być to, że kompilator może włączyć się z własnej inicjatywy; lub że kod jest mały, więc nie ma problemów z pobieraniem kodu.
einpoklum,

3
@einpoklum Z tego powodu kompilator mógł nawet zoptymalizować całą pętlę.
noɥʇʎԀʎzɐɹƆ

197

Zalety

  • Poprzez wstawienie kodu tam, gdzie jest potrzebny, twój program będzie spędzał mniej czasu na wywołaniu funkcji i zwracaniu części. Ma sprawić, że twój kod będzie działał szybciej, nawet gdy będzie większy (patrz poniżej). Wstawianie trywialnych akcesoriów może być przykładem skutecznego wstawiania.
  • Oznaczając go jako wbudowany, możesz umieścić definicję funkcji w pliku nagłówkowym (tzn. Może być zawarta w wielu jednostkach kompilacyjnych, bez narzekań linkera)

Niedogodności

  • Może sprawić, że Twój kod będzie większy (np. Jeśli użyjesz funkcji inline do nie trywialnych funkcji). Jako taki może sprowokować stronicowanie i pokonać optymalizacje kompilatora.
  • Lekko psuje twoją enkapsulację, ponieważ odsłania wewnętrzne przetwarzanie obiektu (ale wtedy również każdy „prywatny” członek). Oznacza to, że nie wolno używać wstawiania we wzorze PImpl.
  • Lekko psuje to enkapsulację 2: wstawianie C ++ jest rozwiązywane podczas kompilacji. Co oznacza, że ​​jeśli zmienisz kod funkcji wstawionej, będziesz musiał ponownie skompilować cały kod, aby się upewnić, że zostanie zaktualizowany (z tego samego powodu unikam wartości domyślnych parametrów funkcji)
  • Gdy użyje się go w nagłówku, powiększy on plik nagłówka, a tym samym rozmyje interesujące informacje (takie jak lista metod klas) kodem, o który nie dba użytkownik (jest to powód, dla którego deklaruję funkcje wstawiane wewnątrz klasa, ale zdefiniuje ją w nagłówku po treści klasy i nigdy w jej treści).

Inlining Magic

  • Kompilator może, ale nie musi, wstawiać funkcje oznaczone jako wbudowane; może również zdecydować się na wstawienie funkcji, które nie są oznaczone jako wbudowane w czasie kompilacji lub łączenia.
  • Inline działa jak kopiowanie / wklejanie kontrolowane przez kompilator, który jest zupełnie inny niż makro preprocesora: Makro będzie wymuszone, spowoduje zanieczyszczenie wszystkich przestrzeni nazw i kodu, nie będzie łatwo debugować, a nawet zostanie wykonane gdyby kompilator uznałby to za nieefektywne.
  • Każda metoda klasy zdefiniowanej w treści samej klasy jest uważana za „wbudowaną” (nawet jeśli kompilator nadal może zdecydować o jej niewłączeniu)
  • Metody wirtualne nie powinny być nieuniknione. Jednak czasami, gdy kompilator może z całą pewnością poznać typ obiektu (tj. Obiekt został zadeklarowany i zbudowany w tym samym ciele funkcji), nawet funkcja wirtualna zostanie wstawiona, ponieważ kompilator zna dokładnie typ obiektu.
  • Metody / funkcje szablonów nie zawsze są wstawiane (ich obecność w nagłówku nie powoduje ich automatycznego wstawiania).
  • Następnym krokiem po „inline” jest metaprogramowanie szablonu. Tj. „Wstawiając” kod w czasie kompilacji, czasami kompilator może wydedukować końcowy wynik funkcji ... Tak więc złożony algorytm może czasami zostać zredukowany do pewnego rodzaju return 42 ;instrukcji. To dla mnie ekstremalne pochylenie . Zdarza się rzadko w prawdziwym życiu, wydłuża czas kompilacji, nie powoduje wzdęcia i przyspiesza kod. Ale podobnie jak graal, nie próbuj go stosować wszędzie, ponieważ większość procesów nie może być rozwiązana w ten sposób ... Mimo to jest to fajne ...
    :-p

powiedziałeś, że to nieznacznie łamie twoje kapsułkowanie. Czy możesz to wyjaśnić na przykładzie?
Destructor

6
@PravasiMeet: It's C ++. Załóżmy, że dostarczasz bibliotekę DLL / bibliotekę współdzieloną klientowi, który się z nią kompiluje. Funkcja inline foo, wykorzystująca zmienną składową X i wykonująca pracę Y, zostanie wstawiona do kodu klienta. Powiedzmy, że musisz dostarczyć zaktualizowaną wersję biblioteki DLL, w której zmieniono zmienną składową na Z, i dodać pracę YY oprócz pracy Y. Klient po prostu kopiuje DLL do swojego projektu i BOOM, ponieważ kod foo w ich pliku binarnym nie ma zaktualizowanego kodu, który napisałeś ... Mimo że klient nie ma legalnego dostępu do twojego prywatnego kodu, inlining czyni go dość „publicznym”.
paercebal,

@paercebal Jeśli chodzi o punkt od drugiego do ostatniego wypunktowania, czy możesz podać przykład, kiedy szablon funkcji nie jest wbudowany? Myślałem, że zawsze były w linii, chociaż nie mam teraz pod ręką referencji (choć wydaje się, że potwierdza to prosty test).
Konrad Rudolph

@KonradRudolph W n4594 widzę: 3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template. W 5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template. I 7.1.2/2: the use of inline keyword is to declare an inline functiontam, gdzie sugeruje się wstawienie ciała funkcji w punkcie wywołania. Więc do wniosku, że nawet jeśli mogą one zachowywać się tak samo, funkcje inline i szablony funkcji są nadal oddzielne, prostopadłe pojęcia, że może być mieszany (tj szablon funkcji inline)
paercebal

enkapsulacja jest zepsuta? Jak to? enkapsulacja jest przeznaczona do programowania faceta (ów), a nie do rzeczywistych obiektów w pamięci. w tym momencie nikogo to nie obchodzi. Nawet jeśli dystrybuujesz bibliotekę, kompilator może zdecydować się na wbudowanie lub nie robić tego samodzielnie. więc w końcu, kiedy dostaniesz nową bibliotekę, musisz ponownie skompilować wszystko, co korzysta z funkcji i obiektów z tej biblioteki.
FalcoGer,

42

W archaicznym C i C ++ inlinejest to register: sugestia (nic więcej niż sugestia) dla kompilatora na temat możliwej optymalizacji.

We współczesnym C ++ inlinemówi linkerowi, że jeśli wiele definicji (nie deklaracji) znajduje się w różnych jednostkach tłumaczeniowych, wszystkie są takie same, a linker może dowolnie je przechowywać i odrzucać wszystkie pozostałe.

inline jest obowiązkowe, jeśli funkcja (bez względu na to, jak złożona lub „liniowa”) jest zdefiniowana w pliku nagłówkowym, aby umożliwić włączenie wielu źródeł bez uzyskania przez linker błędu „wielu definicji”.

Funkcje składowe zdefiniowane wewnątrz klasy są domyślnie „wbudowane”, podobnie jak funkcje szablonów (w przeciwieństwie do funkcji globalnych).

//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

Zwróć uwagę na dołączenie fileA.h do dwóch plików .cpp, co powoduje dwa wystąpienia afunc(). Linker odrzuci jeden z nich. Jeśli nie inlinezostanie określony, linker narzeka.


16

Inlining to propozycja dla kompilatora, którą można zignorować. Jest idealny do małych kawałków kodu.

Jeśli twoja funkcja jest wbudowana, jest ona zasadniczo wstawiana do kodu, w którym jest do niej wywoływana funkcja, zamiast wywoływać osobną funkcję. Może to pomóc z prędkością, ponieważ nie musisz wykonywać rzeczywistego połączenia.

Pomaga również procesorom w przetwarzaniu potokowym, ponieważ nie muszą one ponownie ładować potoku nowymi instrukcjami wywołanymi wywołaniem.

Jedyną wadą jest możliwy większy rozmiar pliku binarnego, ale dopóki funkcje są małe, nie będzie to miało większego znaczenia.

Tego rodzaju decyzje pozostawiam obecnie kompilatorom (cóż, zresztą te inteligentne). Ludzie, którzy je napisali, mają znacznie bardziej szczegółową wiedzę na temat architektur bazowych.


12

Funkcja Inline to technika optymalizacji wykorzystywana przez kompilatory. Można po prostu wstawić słowo kluczowe inline do prototypu funkcji, aby funkcja była wstawiona. Funkcja Inline instruuje kompilator, aby wstawił pełny treść funkcji, gdziekolwiek funkcja ta została użyta w kodzie.

Zalety :-

  1. Nie wymaga narzutu wywołania funkcji.

  2. Oszczędza to również narzutu zmiennych push / pop na stosie, podczas wywoływania funkcji.

  3. Oszczędza to również narzutu wywołania zwrotnego z funkcji.

  4. Zwiększa lokalizację referencji dzięki wykorzystaniu pamięci podręcznej instrukcji.

  5. Po wstawieniu kompilator może również zastosować optymalizację wewnątrzprocesową, jeśli jest to określone. Jest to najważniejszy, w ten sposób kompilator może teraz skupić się na eliminacji martwego kodu, może kłaść większy nacisk na przewidywanie gałęzi, eliminację zmiennych indukcyjnych itp.

Aby sprawdzić więcej na ten temat, kliknij ten link http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html


4
1) To sugestia, a nie instrukcja. 2) Może powodować więcej braków pamięci podręcznej z powodu wzrostu rozmiaru kodu, jeśli często używana jest często używana funkcja
Flexo

3
Czy link na końcu jest Twoim osobistym blogiem? Jeśli tak, należy zadeklarować go jako taki, w przeciwnym razie wygląda jak spam.
Flexo

6

Chciałbym dodać, że funkcje wbudowane są kluczowe podczas budowania biblioteki współdzielonej. Bez wbudowanej funkcji znakowania zostanie ona wyeksportowana do biblioteki w formie binarnej. Będzie również obecny w tabeli symboli, jeśli zostanie wyeksportowany. Z drugiej strony wbudowane funkcje nie są eksportowane ani do plików binarnych biblioteki, ani do tabeli symboli.

Może to mieć krytyczne znaczenie, gdy biblioteka ma być ładowana w czasie wykonywania. Może także uderzyć w biblioteki obsługujące binarnie. W takich przypadkach nie używaj wbudowanego.


@Johnsyweb: uważnie przeczytaj moją odpowiedź. To, co powiedziałeś, jest prawdą, gdy budujesz plik wykonywalny. Ale kompilator nie może po prostu zignorować inlinepodczas budowania biblioteki współdzielonej!
dok

4

Podczas optymalizacji wiele kompilatorów wstawia funkcje, nawet jeśli ich nie zaznaczyłeś. Zasadniczo wystarczy oznaczyć funkcje jako wbudowane, jeśli wiesz coś, czego nie ma kompilator, ponieważ zwykle może on sam podjąć właściwą decyzję.


Wiele kompilatorów też tego nie zrobi, na przykład MSVC tego nie zrobi, chyba że powiesz o tym
paulm

4

inlinepozwala umieścić definicję funkcji w pliku nagłówkowym, a #includeten plik nagłówkowy w wielu plikach źródłowych, bez naruszania reguły jednej definicji.


3

Ogólnie rzecz biorąc, w dzisiejszych czasach każdy nowoczesny kompilator martwiący się o wstawianie czegokolwiek jest właściwie stratą czasu. Kompilator powinien właściwie zoptymalizować wszystkie te kwestie za Ciebie poprzez własną analizę kodu i specyfikację flag optymalizacji przekazanych do kompilatora. Jeśli zależy Ci na szybkości, powiedz kompilatorowi, aby zoptymalizował szybkość. Jeśli zależy ci na przestrzeni, powiedz kompilatorowi, aby zoptymalizował się pod kątem miejsca. Jak wspomniano w innej odpowiedzi, przyzwoity kompilator wstawi się automatycznie, jeśli naprawdę ma sens.

Ponadto, jak powiedzieli inni, użycie inline nie gwarantuje inline niczego. Jeśli chcesz to zagwarantować, będziesz musiał zdefiniować makro zamiast funkcji wbudowanej.

Kiedy wstawiać i / lub definiować makro, aby wymusić włączenie? - Tylko wtedy, gdy masz zademonstrowany i konieczny udowodniony wzrost prędkości dla krytycznej sekcji kodu, o której wiadomo, że ma wpływ na ogólną wydajność aplikacji.


… Jeśli zależy ci na przestrzeni, powiedz kompilatorowi, aby zoptymalizował się pod kątem miejsca - powiedzenie kompilatorowi, aby zoptymalizował pod kątem szybkości, może skutkować mniejszymi plikami binarnymi w C ++ i C. podobnie, powiedzenie kompilatorowi, aby zoptymalizował pod kątem miejsca, może spowodować szybsze wykonanie. te funkcje nie zawsze działają zgodnie z reklamą. ludzie są w stanie lepiej zrozumieć niektóre aspekty swojego programu niż uogólnione interpretacje kompilatora (które również muszą pozostać użyteczne szybko). interwencja człowieka nie musi być złą rzeczą.
justin

3

Nie chodzi tylko o wydajność. Zarówno C ++, jak i C są używane do programowania osadzonego na sprzęcie. Jeśli chcesz na przykład napisać moduł obsługi przerwań, musisz upewnić się, że kod może zostać wykonany natychmiast, bez zamiany dodatkowych rejestrów i / lub stron pamięci. Właśnie wtedy przydaje się inline. Dobre kompilatory wykonują pewne „inliniowanie”, gdy potrzebna jest prędkość, ale „inline” je zmusza.


1

Wpadłem w ten sam problem z wstawianiem funkcji do bibliotek. Wygląda na to, że funkcje wbudowane nie są kompilowane w bibliotece. w wyniku tego linker zgłasza błąd „niezdefiniowane odwołanie”, jeśli plik wykonywalny chce użyć wbudowanej funkcji biblioteki. (przydarzyło mi się kompilowanie źródła Qt z gcc 4.5.


1

Dlaczego nie włączyć domyślnie wszystkich funkcji? Ponieważ jest to kompromis inżynieryjny. Istnieją co najmniej dwa rodzaje „optymalizacji”: przyspieszenie programu i zmniejszenie rozmiaru (powierzchni pamięci) programu. Inlining ogólnie przyspiesza. Pozbywa się narzutu wywołania funkcji, unikając pchania, a następnie wyciągania parametrów ze stosu. Jednak powoduje to również zwiększenie pamięci programu, ponieważ każde wywołanie funkcji musi teraz zostać zastąpione pełnym kodem funkcji. Aby jeszcze bardziej skomplikować sprawę, pamiętaj, że procesor przechowuje często używane fragmenty pamięci w pamięci podręcznej procesora, zapewniając ultra szybki dostęp. Jeśli sprawisz, że obraz pamięci programu będzie wystarczająco duży, Twój program nie będzie w stanie efektywnie wykorzystywać pamięci podręcznej, aw najgorszym przypadku wbudowanie może spowolnić program.


1

Nasz profesor informatyki namawiał nas, aby nigdy nie używać inline w programie c ++. Zapytany, dlaczego, uprzejmie wyjaśnił nam, że nowoczesne kompilatory powinny wykryć, kiedy automatycznie używać wbudowanego.

Tak, inline może być techniką optymalizacji, która może być stosowana wszędzie tam, gdzie jest to możliwe, ale najwyraźniej jest to już coś dla ciebie zrobione, ilekroć możliwe jest wstawienie funkcji w każdym razie.


5
Twój profesor niestety całkowicie się myli. inlinew C ++ ma dwa bardzo różne znaczenia - tylko jedno z nich jest związane z optymalizacją, a twój profesor ma rację w tym zakresie. Jednak drugie znaczenie inlinejest często konieczne, aby spełnić regułę jednej definicji .
Konrad Rudolph

-1

Wniosek z innej dyskusji tutaj:

Czy są jakieś wady związane z funkcjami wbudowanymi?

Najwyraźniej nie ma nic złego w korzystaniu z funkcji wbudowanych.

Warto jednak zwrócić uwagę na następujące punkty!

  • Nadużywanie wbudowania może spowolnić programy. W zależności od wielkości funkcji wstawianie jej może powodować zwiększenie lub zmniejszenie rozmiaru kodu. Wprowadzenie bardzo małej funkcji akcesorium zwykle zmniejsza rozmiar kodu, natomiast wprowadzenie bardzo dużej funkcji może radykalnie zwiększyć rozmiar kodu. Na nowoczesnych procesorach mniejszy kod zwykle działa szybciej ze względu na lepsze wykorzystanie pamięci podręcznej instrukcji. - Wytyczne Google

  • Korzyści prędkości funkcji wbudowanych zmniejszają się wraz ze wzrostem wielkości funkcji. W pewnym momencie narzut wywołania funkcji staje się niewielki w porównaniu do wykonania treści funkcji, a korzyść jest tracona - Źródło

  • Istnieje kilka sytuacji, w których funkcja wbudowana może nie działać:

    • Dla funkcji zwracającej wartości; jeśli istnieje instrukcja return.
    • Dla funkcji nie zwracającej żadnych wartości; jeśli istnieje instrukcja pętli, przełącznika lub goto.
    • Jeśli funkcja jest rekurencyjna. -Źródło
  • Słowo __inlinekluczowe powoduje wstawienie funkcji tylko wtedy, gdy podasz opcję optymalizacji. Jeśli określono optymalizację, to, czy ma __inlinebyć honorowana, zależy od ustawienia opcji optymalizatora wbudowanego. Domyślnie opcja wbudowana działa przy każdym uruchomieniu optymalizatora. Jeśli określisz optymalizację, musisz również określić opcję noinline, jeśli chcesz, aby __inlinesłowo kluczowe było ignorowane. -Źródło


3
Byłoby prawdą, gdyby inline był poleceniem, a nie wskazówką dla kompilatora. Kompilator faktycznie decyduje, co wstawić.
Martin York,

1
@LokiAstari Wiem, że wbudowane jest żądanie do kompilatora. Mój argument brzmi: jeśli wskazówka dla kompilatora powinna pozostawić to kompilatorowi, aby zdecydował, co jest najlepsze. Po co używać inline w jakikolwiek sposób, nawet jeśli używasz inline, to kompilator podejmie ostateczną decyzję. Zastanawiam się również, czy mój Microsoft wprowadził _forceinline.
Krishna Oza

@krish_oza: Mój komentarz tutaj. Chodzi o tę odpowiedź. Odpowiedź tutaj jest całkowicie błędna. Ponieważ kompilator ignoruje inlinesłowo kluczowe przy ustalaniu, czy wstawić kod, czy wszystkie powyższe punkty są błędne. Byłyby prawdziwe, gdyby kompilator użył słowa kluczowego do wstawiania (służy on tylko do oznaczania zaznaczenia wielu definicji do celów łączenia).
Martin York
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.