Vantaggi delle funzioni inline in C ++?


254

Quali sono i vantaggi / gli svantaggi dell'utilizzo delle funzioni inline in C ++? Vedo che aumenta solo le prestazioni per il codice prodotto dal compilatore, ma con i compilatori ottimizzati di oggi, CPU veloci, memoria enorme ecc. (Non come nel 1980 <dove la memoria era scarsa e tutto doveva adattarsi a 100 KB di memoria) vantaggi hanno davvero oggi?


48
Questa è una di quelle domande in cui la conoscenza comune è sbagliata. Tutti hanno risposto con la risposta standard di Comp Sci. (L'Inline risparmia sui costi delle chiamate di funzione ma aumenta la dimensione del codice). Sciocchezze. Fornisce un semplice meccanismo per il compilatore per applicare più OTTIMIZZAZIONI.
Martin York,

37
Questa è una di quelle risposte che si presentano come commenti. Se non ti piacciono le risposte pubblicate, pubblica la tua risposta e guarda come va.
Dave Van den Eynde

10
La base di questa domanda è imperfetta. Le funzioni inline C ++ hanno poco a che fare con i compilatori in linea durante la compilazione. È un peccato che inlinesia una parola chiave c ++ e che il rivestimento sia una tecnica di ottimizzazione del compilatore. Vedi questa domanda " quando dovrei scrivere la parola chiave inlineper una funzione / metodo " per la risposta corretta.
deft_code

3
@JoseVega Il tuo link è stato alterato - il link corrente è exforsys.com/tutorials/c-plus-plus/inline-functions.html

Risposte:


143

Le funzioni incorporate sono più veloci perché non è necessario spingere e far apparire le cose nello stack / off come parametri e l'indirizzo di ritorno; tuttavia, rende il tuo binario leggermente più grande.

Fa una differenza significativa? Non abbastanza evidente su hardware moderno per la maggior parte. Ma può fare la differenza, il che è abbastanza per alcune persone.

Contrassegnare qualcosa in linea non ti dà la garanzia che sarà in linea. È solo un suggerimento per il compilatore. A volte non è possibile, ad esempio quando si ha una funzione virtuale o quando è coinvolta la ricorsione. E a volte il compilatore sceglie semplicemente di non usarlo.

Ho potuto vedere una situazione come questa fare una differenza rilevabile:

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
Come sospettavo, l'allineamento non fa alcuna differenza rispetto a quanto sopra. Compilato con gcc 4.01. Versione 1 costretta a utilizzare l'interno: 48.318u 1.042s 5: 51.39 99.4% 0 + 0k 0 + 0io 0pf + 0w Versione 2 imposto nessun allineamento 348.311u 1.019s 5: 52.31 99.1% 0 + 0k 0 + 0io 0pf + 0w Questo è un buon esempio di conoscenza comune è sbagliato.
Martin York,

36
mentre la chiamata stessa conta davvero, questo è solo il guadagno minore che ottieni usando in linea. Il vantaggio principale è che il compilatore ora vede dove i puntatori non si aliasano l'un l'altro, dove le variabili del chiamante finiscono nella chiamata e così via. Pertanto, la seguente ottimizzazione è ciò che conta di più.
Johannes Schaub - litb

31
Probabilmente non fa differenza in questo snippet poiché il risultato della funzione non viene mai utilizzato né la funzione ha effetti collaterali. Vediamo un miglioramento misurabile delle prestazioni nell'inline nell'elaborazione delle immagini.
plinto

4
La ragione per nessuna differenza potrebbe essere che il compilatore potrebbe essere incorporato da solo; o che il codice è piccolo, quindi non ci sono problemi di prefetch del codice.
einpoklum,

3
@einpoklum Il compilatore potrebbe anche aver ottimizzato l'intero ciclo a causa di ciò.
noɥʇʎԀʎzɐɹƆ

197

vantaggi

  • Integrando il codice dove è necessario, il programma impiegherà meno tempo nella chiamata di funzione e restituirà parti. Dovrebbe rendere il tuo codice più veloce, anche se diventa più grande (vedi sotto). L'allineamento di accessori banali potrebbe essere un esempio di allineamento efficace.
  • Contrassegnandolo come in linea, è possibile inserire una definizione di funzione in un file di intestazione (ovvero può essere inclusa in più unità di compilazione, senza che il linker si lamenti)

svantaggi

  • Può ingrandire il tuo codice (cioè se usi inline per funzioni non banali). Come tale, potrebbe provocare il paging e sconfiggere le ottimizzazioni dal compilatore.
  • Rompe leggermente l'incapsulamento perché espone l'interno dell'elaborazione dell'oggetto (ma poi anche ogni membro "privato" lo farebbe). Ciò significa che non è necessario utilizzare la linea in un modello PImpl.
  • Si rompe leggermente l'incapsulamento 2: l'inline c ++ viene risolto al momento della compilazione. Ciò significa che se dovessi modificare il codice della funzione incorporata, dovrai ricompilare tutto il codice utilizzandolo per essere sicuro che verrà aggiornato (per lo stesso motivo, evito i valori predefiniti per i parametri della funzione)
  • Se utilizzato in un'intestazione, ingrandisce il file dell'intestazione e quindi diluisce informazioni interessanti (come l'elenco dei metodi di una classe) con il codice di cui l'utente non si preoccupa (questo è il motivo per cui dichiaro funzioni incorporate all'interno di un classe, ma lo definirà in un'intestazione dopo il corpo della classe e mai all'interno del corpo della classe).

Magia Inline

  • Il compilatore può incorporare o meno le funzioni contrassegnate come incorporate; può anche decidere di incorporare funzioni non contrassegnate come incorporate al momento della compilazione o del collegamento.
  • Inline funziona come una copia / incolla controllata dal compilatore, che è abbastanza diversa da una macro pre-processore: la macro verrà forzatamente incorporata, inquinerà tutti gli spazi dei nomi e il codice, non sarà facilmente debuggabile e verrà eseguita anche se il compilatore lo avesse giudicato inefficiente.
  • Ogni metodo di una classe definita all'interno del corpo della classe stessa è considerato "inline" (anche se il compilatore può ancora decidere di non incorporarlo
  • I metodi virtuali non dovrebbero essere inlinabili. Tuttavia, a volte, quando il compilatore può sapere con certezza il tipo di oggetto (ovvero l'oggetto è stato dichiarato e costruito all'interno dello stesso corpo della funzione), anche una funzione virtuale verrà sottolineata perché il compilatore conosce esattamente il tipo di oggetto.
  • I metodi / le funzioni del modello non sono sempre in linea (la loro presenza in un'intestazione non li renderà automaticamente in linea).
  • Il passo successivo dopo "inline" è la metaprogrammazione del modello. Vale a dire "incorporando" il codice in fase di compilazione, a volte, il compilatore può dedurre il risultato finale di una funzione ... Quindi a volte un algoritmo complesso può essere ridotto a una sorta di return 42 ;istruzione. Questo è per me un allineamento estremo . Succede raramente nella vita reale, allunga il tempo di compilazione, non gonfia il tuo codice e renderà il tuo codice più veloce. Ma come il Graal, non cercare di applicarlo ovunque perché la maggior parte dell'elaborazione non può essere risolta in questo modo ... Comunque, è comunque bello ...
    :-p

hai detto che rompe leggermente l'incapsulamento. Per favore, puoi spiegarlo usando un esempio?
Distruttore

6
@PravasiMeet: è C ++. Supponiamo che tu fornisca una libreria DLL / condivisa a un client, che compila contro di essa. La funzione inline foo, usando la variabile membro X e facendo il lavoro Y verrà incorporata nel codice del client. Diciamo che è necessario fornire una versione aggiornata della DLL in cui è stata modificata la variabile membro in Z e aggiungere un lavoro YY oltre al lavoro Y. Il client copia semplicemente la DLL nel proprio progetto e BOOM, perché il codice di foo nel loro binario non è il codice aggiornato che hai scritto ... Nonostante il client non abbia accesso legale al tuo codice privato, l'inline lo rende abbastanza "pubblico".
Paercebal,

@paercebal Per quanto riguarda il tuo penultimo punto, puoi fare un esempio di quando un modello di funzione non è in linea? Ho pensato che fossero sempre in linea, anche se non ho un riferimento utile ora (un semplice test sembra confermarlo però).
Konrad Rudolph,

@KonradRudolph In n4594 vedo: 3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template. Al 5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template. E 7.1.2/2: the use of inline keyword is to declare an inline functiondove è un suggerimento per incorporare il corpo della funzione nel punto di chiamata. Quindi, concludo che anche se possono comportarsi allo stesso modo, le funzioni in linea e i modelli di funzione sono ancora distinti, nozioni ortogonali che possono essere mescolate (cioè modello di funzione in linea)
paercebal

l'incapsulamento è rotto? Come mai? l'incapsulamento è per la programmazione del / i ragazzo / i, non per gli oggetti reali in memoria. a quel punto a nessuno importa. Anche se distribuisci una libreria, il compilatore può scegliere di incorporarlo o di non farlo da solo. così alla fine quando ottieni una nuova lib, devi solo ricompilare tutto ciò che usa le funzioni e gli oggetti di quella libreria.
FalcoGer,

42

In C e C ++ arcaici, inlineè come register: un suggerimento (nient'altro che un suggerimento) al compilatore su una possibile ottimizzazione.

Nel moderno C ++, inlinedice al linker che, se più definizioni (non dichiarazioni) si trovano in unità di traduzione diverse, sono tutte uguali e il linker può conservarne liberamente una e scartare tutte le altre.

inline è obbligatorio se una funzione (non importa quanto complessa o "lineare") sia definita in un file di intestazione, per consentire a più origini di includerla senza ottenere un errore di "definizione multipla" dal linker.

Le funzioni membro definite all'interno di una classe sono "in linea" per impostazione predefinita, così come le funzioni modello (contrariamente alle funzioni globali).

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

Notare l'inclusione di fileA.h in due file .cpp, risultando in due istanze di afunc(). Il linker eliminerà uno di essi. Se non inlineviene specificato, il linker si lamenterà.


16

L'allineamento è un suggerimento per il compilatore che è libero di ignorare. È ideale per piccoli frammenti di codice.

Se la tua funzione è incorporata, viene sostanzialmente inserita nel codice in cui viene effettuata la chiamata di funzione, anziché chiamare effettivamente una funzione separata. Questo può aiutare con la velocità in quanto non è necessario effettuare la chiamata effettiva.

Aiuta anche le CPU a pipeline in quanto non devono ricaricare la pipeline con nuove istruzioni causate da una chiamata.

L'unico svantaggio è la possibilità di aumentare le dimensioni binarie ma, fintanto che le funzioni sono piccole, questo non importerà troppo.

Al giorno d'oggi tendo a lasciare questo tipo di decisioni ai compilatori (beh, comunque quelli intelligenti). Le persone che le hanno scritte tendono ad avere una conoscenza molto più dettagliata delle architetture sottostanti.


12

La funzione incorporata è la tecnica di ottimizzazione utilizzata dai compilatori. Si può semplicemente anteporre una parola chiave incorporata al prototipo di funzione per rendere una funzione incorporata. La funzione inline indica al compilatore di inserire il corpo completo della funzione ovunque tale funzione sia stata utilizzata nel codice.

Vantaggi: -

  1. Non richiede l'overhead di chiamata di funzione.

  2. Inoltre salva l'overhead delle variabili push / pop nello stack, mentre la funzione chiama.

  3. Inoltre salva un overhead della chiamata di ritorno da una funzione.

  4. Aumenta la località di riferimento utilizzando la cache delle istruzioni.

  5. Dopo che il compilatore in-lining può anche applicare l'ottimizzazione intra-procedurale se specificato. Questo è il più importante, in questo modo il compilatore può ora concentrarsi sull'eliminazione del codice morto, può dare più stress alla previsione del ramo, all'eliminazione della variabile di induzione ecc.

Per saperne di più, puoi seguire questo link http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html


4
1) È un suggerimento, non un'istruzione 2) Può causare più errori di cache a causa dell'aumento delle dimensioni del codice se una funzione di uso comune è incorporata molto
Flexo

3
Il link alla fine è il tuo blog personale? Se lo è, dovresti dichiararlo come tale, altrimenti sembra spam.
Flexo

6

Vorrei aggiungere che le funzioni incorporate sono cruciali quando si crea una libreria condivisa. Senza la funzione di marcatura incorporata, verrà esportata nella libreria in forma binaria. Sarà anche presente nella tabella dei simboli, se esportato. Dall'altro lato, le funzioni incorporate non vengono esportate, né nei file binari della libreria né nella tabella dei simboli.

Può essere fondamentale quando si intende caricare la libreria in fase di esecuzione. Può anche colpire librerie compatibili con binari compatibili. In questi casi non usare in linea.


@Johnsyweb: leggi attentamente la mia risposta. Quello che hai detto è vero quando stai costruendo un eseguibile. Ma il compilatore non può semplicemente ignorare inlinequando si costruisce una libreria condivisa!
doc

4

Durante l'ottimizzazione molti compilatori incorporeranno le funzioni anche se non sono state contrassegnate. In genere è necessario contrassegnare le funzioni come in linea solo se si conosce qualcosa che il compilatore non ha, poiché di solito può prendere la decisione corretta stessa.


Anche molti compilatori non lo faranno, MSVC per esempio non lo farà a meno che tu non lo dica a
paulm

4

inlineconsente di inserire una definizione di funzione in un file di intestazione e #includetale file di intestazione in più file di origine senza violare la regola di una definizione.


3

In generale, in questi giorni con qualsiasi compilatore moderno preoccuparsi di inserire qualcosa è praticamente una perdita di tempo. Il compilatore dovrebbe effettivamente ottimizzare tutte queste considerazioni per te attraverso la propria analisi del codice e la specifica dei flag di ottimizzazione passati al compilatore. Se ti interessa la velocità, informa il compilatore di ottimizzare la velocità. Se ti interessa lo spazio, comunica al compilatore di ottimizzare lo spazio. Come è stata accennata un'altra risposta, un compilatore decente si inserirà automaticamente anche se ha davvero senso.

Inoltre, come altri hanno affermato, l'uso in linea non garantisce in linea nulla. Se si desidera garantirlo, per farlo è necessario definire una macro anziché una funzione in linea.

Quando incorporare e / o definire una macro per forzare l'inclusione? - Solo quando si ha un aumento dimostrato e necessario della velocità dimostrata per una sezione critica del codice che influisce sulle prestazioni complessive dell'applicazione.


... Se ti interessa lo spazio, informa il compilatore di ottimizzare per lo spazio : dire al compilatore di ottimizzare per la velocità può comportare binari più piccoli con C ++ e C. allo stesso modo, dire al compilatore di ottimizzare per lo spazio può portare a un'esecuzione più veloce. ehi, queste funzionalità non funzionano sempre come pubblicizzato. gli umani hanno la capacità di comprendere alcuni aspetti del loro programma meglio delle interpretazioni generalizzate di un compilatore (che devono anche rimanere utilizzabili velocemente). l'intervento umano non deve essere una brutta cosa.
Giusto il

3

Non si tratta solo di prestazioni. Sia C ++ che C sono usati per la programmazione integrata, posizionati sopra l'hardware. Se, ad esempio, dovessi scrivere un gestore di interrupt, devi assicurarti che il codice possa essere eseguito immediatamente, senza che vengano scambiati registri e / o pagine di memoria aggiuntivi. Questo è quando inline è utile. I bravi compilatori fanno un po 'di "inline" se stessi quando è necessaria la velocità, ma "inline" li costringe.


1

Affronta lo stesso problema con le funzioni di allineamento in così librerie. Sembra che le funzioni incorporate non vengano compilate nella libreria. di conseguenza il linker emette un errore di "riferimento non definito", se un eseguibile desidera utilizzare la funzione incorporata della libreria. (mi è successo compilando il sorgente Qt con gcc 4.5.


1

Perché non rendere tutte le funzioni in linea per impostazione predefinita? Perché è un compromesso di ingegneria. Esistono almeno due tipi di "ottimizzazione": velocizzare il programma e ridurre le dimensioni (footprint di memoria) del programma. L'allineamento generalmente accelera le cose. Elimina l'overhead della chiamata di funzione, evitando di spingere e quindi estrarre i parametri dallo stack. Tuttavia, aumenta anche l'impronta di memoria del programma, poiché ogni chiamata di funzione deve ora essere sostituita con il codice completo della funzione. Per rendere le cose ancora più complicate, ricorda che la CPU memorizza blocchi di memoria utilizzati di frequente in una cache della CPU per un accesso ultra-rapido. Se rendi l'immagine di memoria del programma abbastanza grande, il tuo programma non sarà in grado di utilizzare la cache in modo efficiente e, nel peggiore dei casi, l'allineamento potrebbe effettivamente rallentare il programma.


1

Il nostro professore di informatica ci ha esortato a non usare mai in linea in un programma c ++. Quando è stato chiesto perché, ci ha gentilmente spiegato che i compilatori moderni dovrebbero rilevare quando utilizzare automaticamente in linea.

Quindi sì, l'inline può essere una tecnica di ottimizzazione da utilizzare laddove possibile, ma a quanto pare questo è qualcosa che è già stato fatto per te ogni volta che è possibile incorporare una funzione comunque.


5
Purtroppo il tuo professore ha completamente torto. inlinein C ++ ha due significati molto distinti: solo uno di questi è legato all'ottimizzazione, e il tuo professore ha ragione a riguardo. Tuttavia, il secondo significato di inlineè spesso necessario per soddisfare la regola della definizione unica .
Konrad Rudolph,

-1

Conclusione da un'altra discussione qui:

Ci sono degli svantaggi con le funzioni incorporate?

Apparentemente, non c'è niente di sbagliato nell'usare le funzioni in linea.

Ma vale la pena notare i seguenti punti!

  • L'uso eccessivo di inline può effettivamente rallentare i programmi. A seconda della dimensione di una funzione, l'allineamento può causare l'aumento o la riduzione della dimensione del codice. L'inserimento di una funzione di accesso molto piccola di solito riduce la dimensione del codice, mentre l'aggiunta di una funzione molto grande può aumentare notevolmente la dimensione del codice. Sui processori moderni il codice più piccolo di solito viene eseguito più velocemente grazie al migliore utilizzo della cache delle istruzioni. - Linee guida di Google

  • I vantaggi di velocità delle funzioni in linea tendono a diminuire man mano che la funzione aumenta di dimensioni. Ad un certo punto il sovraccarico della chiamata di funzione diventa piccolo rispetto all'esecuzione del corpo della funzione e il vantaggio viene perso - Fonte

  • Esistono alcune situazioni in cui una funzione incorporata potrebbe non funzionare:

    • Per una funzione che restituisce valori; se esiste una dichiarazione di ritorno.
    • Per una funzione che non restituisce alcun valore; se esiste un'istruzione loop, switch o goto.
    • Se una funzione è ricorsiva. -Fonte
  • La __inlineparola chiave provoca l'inserimento di una funzione solo se si specifica l'opzione di ottimizzazione. Se viene specificato l'ottimizzazione, l' __inlineonorabilità dipende dall'impostazione dell'opzione di ottimizzazione in linea. Per impostazione predefinita, l'opzione incorporata è attiva ogni volta che viene eseguito l'ottimizzatore. Se si specifica l'ottimizzazione, è necessario specificare anche l'opzione noinline se si desidera __inlineignorare la parola chiave. -Fonte


3
Sarebbe vero se l'inline fosse un comando e non un suggerimento per il compilatore. Il compilatore decide effettivamente cosa incorporare.
Martin York,

1
@LokiAstari So che inline è richiesta al compilatore. La mia tesi è che se il suo suggerimento per il compilatore dovremmo lasciarlo al compilatore per decidere quale sia la cosa migliore. Perché usare inline in ogni modo anche se usi inline è comunque il compilatore che prenderà la decisione finale. Mi chiedo anche che la mia Microsoft abbia introdotto _forceinline.
Krishna Oza,

@krish_oza: il mio commento qui. Riguarda questa risposta. La risposta qui è completamente sbagliata. Perché il compilatore ignora la inlineparola chiave per determinare se incorporare o meno il codice tutti i punti sopra indicati sono errati. Sarebbero vere se il compilatore usasse la parola chiave per inline (viene usato solo per determinare la marcatura di definizioni multiple a scopo di collegamento).
Martin York,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.