Quando utilizzare la funzione inline e quando non utilizzarla?


185

So che inline è un suggerimento o una richiesta al compilatore e viene utilizzato per evitare le spese generali per le chiamate di funzione.

Quindi, su quale base si può determinare se una funzione è candidata per l'inline o no? In quale caso si dovrebbe evitare di allinearsi?


11
inlineè per il nuovo arrivato C ++ cosa CFLAGSsono per il nuovo arrivato Gentoo: no, compilare -O3 -funroll-loops -finline-functionsnon farà volare il tuo vecchio Pentium;)
Gregory Pakosz,

1
Un motivo per non utilizzare inline è che alcuni debugger non ti permetteranno di impostare un punto di interruzione o passare a una funzione incorporata.
Rob deFriesse,


5
Non è necessario determinare se una funzione deve essere incorporata o meno. Lascia che il compilatore lo faccia; è meglio di quello che sei (e può incorporare le funzioni in modo selettivo in base all'ambiente di ogni chiamata).
David Thornley,

@DavidThornley A volte, anche con il flag O3 impostato, il compilatore non incorpora la funzione se la definizione è nel file cpp. Quindi, la regola del pollice che seguo è quella di incorporare una fodera e anche quelle funzioni senza loop.
talekeDskobeDa

Risposte:


210

Evitare il costo di una chiamata di funzione è solo metà della storia.

fare:

  • utilizzare inlineinvece di#define
  • funzioni molto piccole sono buoni candidati per inline: codice più veloce ed eseguibili più piccoli (più possibilità di rimanere nella cache del codice)
  • la funzione è piccola e chiamata molto spesso

non:

  • funzioni di grandi dimensioni: porta a eseguibili più grandi, che compromettono significativamente le prestazioni indipendentemente dall'esecuzione più rapida che risulta dal sovraccarico di chiamata
  • funzioni incorporate associate a I / O
  • la funzione viene usata raramente
  • costruttori e distruttori: anche se vuoto, il compilatore genera codice per loro
  • interruzione della compatibilità binaria durante lo sviluppo di librerie:
    • incorporare una funzione esistente
    • modificare una funzione inline o rendere non inline una funzione inline: la versione precedente della libreria chiama la vecchia implementazione

quando si sviluppa una libreria, al fine di rendere estensibile una classe in futuro è necessario:

  • aggiungi un distruttore virtuale non in linea anche se il corpo è vuoto
  • rendere tutti i costruttori non in linea
  • scrivere implementazioni non in linea del costruttore della copia e dell'operatore di assegnazione a meno che la classe non possa essere copiata per valore

Ricorda che la inlineparola chiave è un suggerimento per il compilatore: il compilatore può decidere di non incorporare una funzione e può decidere di incorporare funzioni che non sono state contrassegnate inlinein primo luogo. In genere evito di contrassegnare la funzione inline(a parte forse quando si scrivono funzioni molto piccole).

Per quanto riguarda le prestazioni, l'approccio saggio è (come sempre) il profilo dell'applicazione, quindi alla fine inlineun insieme di funzioni che rappresentano un collo di bottiglia.

Riferimenti:


EDIT: Bjarne Stroustrup, Il linguaggio di programmazione C ++:

Una funzione può essere definita come inline. Per esempio:

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

L' inlineidentificatore indica al compilatore che dovrebbe tentare di generare il codice per una chiamata di fac()inline anziché stabilire una volta il codice per la funzione e quindi chiamare attraverso il normale meccanismo di chiamata della funzione. Un compilatore intelligente può generare la costante 720per una chiamata fac(6). La possibilità di funzioni inline reciprocamente ricorsive, funzioni inline che ricorrono o meno in base all'input, ecc., Rende impossibile garantire che ogni chiamata di una inlinefunzione sia effettivamente incorporata. Il grado di intelligenza di un compilatore non può essere legiferato, quindi un compilatore potrebbe generare 720, un altro 6 * fac(5)e un altro ancora una chiamata non incorporata fac(6).

Per rendere possibile l'allineamento in assenza di funzioni di compilazione e collegamento insolitamente intelligenti, la definizione - e non solo la dichiarazione - di una funzione inline deve essere compresa (§9.2). Un inlineespecifier non influisce sulla semantica di una funzione. In particolare, una funzione inline ha ancora un indirizzo univoco e quindi ha staticvariabili (§7.1.2) di una funzione inline.

EDIT2: ISO-IEC 14882-1998, 7.1.2 Identificatori di funzione

Una dichiarazione di funzione (8.3.5, 9.3, 11.4) con un inlineidentificatore dichiara una funzione incorporata. L'identificatore in linea indica all'implementazione che la sostituzione in linea del corpo della funzione nel punto di chiamata deve essere preferita al normale meccanismo di chiamata di funzione. Non è necessaria un'implementazione per eseguire questa sostituzione in linea al punto di chiamata; tuttavia, anche se questa sostituzione in linea viene omessa, le altre regole per le funzioni in linea definite da 7.1.2 devono comunque essere rispettate.


34
inlineè molto più di un suggerimento per il compilatore. Cambia le regole della lingua su più definizioni. Inoltre, disporre di dati statici non è un motivo in ghisa per evitare di incorporare una funzione. L'implementazione è obbligata ad allocare un singolo oggetto statico per ciascuna funzione statica indipendentemente dal fatto che la funzione sia dichiarata inlineo meno. Le classi sono ancora estensibili se hanno costruttori in linea e distruttori virtuali. E il distruttore di parentesi graffe vuote è l'unica funzione virtuale che a volte è una buona idea lasciare in linea.
CB Bailey,

2
È un suggerimento nel senso che la funzione non finisce necessariamente in linea (ma l'inglese non è la mia lingua madre). Per quanto riguarda la statica nelle funzioni contrassegnate inline, il risultato è che la funzione non viene incorporata: si paga il prezzo per la chiamata e anche ogni unità di traduzione che include e chiama la funzione ottiene la propria copia del codice e delle variabili statiche. La ragione per non includere costruttori e distruttori durante lo sviluppo di una libreria è la compatibilità binaria con le versioni future della tua biblioteca
Gregory Pakosz,

14
Non è corretto definirlo un "suggerimento per il compilatore". In realtà, le non inlinefunzioni possono essere sottolineate se il compilatore ne ha voglia. E le inlinefunzioni non verranno incorporate se il compilatore decide di non incorporarle. Come ha detto Charles Bailey, cambia le regole della lingua. Piuttosto che pensarlo come un suggerimento di ottimizzazione, è più accurato pensarlo come un concetto completamente diverso. La inlineparola chiave indica al compilatore di consentire più definizioni e nient'altro. L'ottimizzazione "inline" può essere applicata a quasi tutte le funzioni, indipendentemente dal fatto che sia contrassegnata o meno inline.
jalf

26
È solo che, quando Stroustrup scrive "lo specificatore inline è un suggerimento per il compilatore", sono sorpreso di essere accusato di averlo citato. Ad ogni modo, ho trascorso abbastanza tempo a fare del mio meglio per sostenere questa risposta con il maggior numero possibile di riferimenti
Gregory Pakosz,

2
@GregoryPakosz: Ma non tutti usiamo inlineper ottenere funzioni in linea. A volte vogliamo altri vantaggi, come aggirare ODR.
Razze di leggerezza in orbita

57

inlineha ben poco a che fare con l'ottimizzazione. inlineè un'istruzione per il compilatore di non produrre un errore se la funzione fornita dalla definizione si verifica più volte nel programma e promette che la definizione si verificherà in ogni traduzione che viene utilizzata e ovunque appaia avrà esattamente la stessa definizione.

Date le regole di cui sopra, inlineè adatto per funzioni brevi il cui corpo non necessita di includere dipendenze extra su ciò di cui solo una dichiarazione avrebbe bisogno. Ogni volta che la definizione viene incontrata, deve essere analizzata e il codice per il suo corpo può essere generato, quindi implica un sovraccarico del compilatore su una funzione definita solo una volta in un singolo file di origine.

Un compilatore può incorporare (ovvero sostituire una chiamata alla funzione con il codice che esegue quell'azione di quella funzione) qualsiasi chiamata di funzione che sceglie. In passato, "ovviamente" non poteva incorporare una funzione che non era dichiarata nella stessa unità di traduzione della chiamata, ma con l'uso crescente dell'ottimizzazione del tempo di collegamento, anche questo non è vero ora. Altrettanto vero è il fatto che le funzioni contrassegnate inlinepotrebbero non essere incorporate.


Ho la sensazione che questa sia più una felice coincidenza che una caratteristica intenzionale del C ++. L'idea è molto simile alle variabili globali "statiche" di C. È comunque una risposta molto interessante. Vorrei che avessero appena usato una parola chiave come "interno" per indicare il collegamento interno.
Rehno Lindeque,

+1. @Rehno: non sono proprio sicuro di quello che stai dicendo. Cosa c'entra il collegamento con la inlineparola chiave? E cos'è una felice coincidenza?
jalf

@jalf: Leggendo il mio commento a posteriori, mi rendo conto che è piuttosto vago e non ben pensato. La definizione della stessa funzione in più file provoca un errore del linker che può essere contrastato dichiarando la funzione "statica". Tuttavia, "inline" consente di fare la stessa cosa con sottili differenze che in realtà non ottengono un collegamento interno come "statico". Ho il sospetto che questa sia in realtà più una coincidenza perché gli implementatori / designer del linguaggio hanno capito che dovranno fare qualcosa di speciale con le funzioni dichiarate nei file di intestazione e che sono state trasferite su "inline".
Rehno Lindeque,

4
Non sono sicuro del motivo per cui il tuo commento ha ottenuto così tanti voti positivi, dal momento che le prestazioni sono la ragione principale per utilizzare in linea.
gast128,

10

Dire al compilatore di incorporare una funzione è un'ottimizzazione e la regola più importante dell'ottimizzazione è che l'ottimizzazione prematura è la radice di tutti i mali. Scrivi sempre un codice chiaro (usando algoritmi efficienti), quindi profila il tuo programma e ottimizza solo le funzioni che richiedono troppo tempo.

Se trovi che una particolare funzione è molto breve e semplice e viene chiamata decine di migliaia di volte in un circuito interno stretto, potrebbe essere un buon candidato.

Potresti essere sorpreso, però - molti compilatori C ++ incorporeranno automaticamente piccole funzioni per te - e potrebbero ignorare anche la tua richiesta di inline.


In effetti, ho il sospetto che alcuni compilatori ignorino completamente "inline" completamente e rispondano solo a "__inline" o "__force_inline". Suppongo che questo sia per scoraggiare gli abusi!
Rehno Lindeque,

Di solito non è il caso. inline è solo un suggerimento, ma è un suggerimento che molti compilatori prendono sul serio. È possibile impostare il compilatore in modo che emetta il linguaggio assembly assieme al codice oggetto ( /FAcsin Visual Studio, -sin GCC) per vedere esattamente cosa fa. Nella mia esperienza, entrambi quei compilatori pesano pesantemente la parola chiave inline.
Crashworks,

1
È interessante, perché nella mia esperienza né g ++ né VC pesano inlineaffatto le parole chiave. Cioè, se vedi la funzione inline e rimuovi l'identificatore inlineda essa, rimarrà comunque inline. Se hai esempi specifici del contrario, condividili!
Pavel Minaev,

4
in che modo la inlineparola chiave impedisce "cancella codice"? La parola chiave in "ottimizzazione prematura" è prematura , non ottimizzazione. Dire che dovresti evitare attivamente * le ottimizzazioni è solo spazzatura. Il punto di quella citazione è che dovresti evitare le ottimizzazioni che potrebbero non essere necessarie e avere effetti collaterali dannosi sul codice (come renderlo meno gestibile). Non riesco a vedere come la inlineparola chiave renderà il codice meno gestibile o come possa essere dannoso aggiungerlo a una funzione.
jalf

3
jalf, talvolta incorporando una funzione renderai il tuo codice più lento, non più veloce. Un esempio è quando la funzione viene chiamata da diversi punti nel codice; se la funzione non è incorporata, potrebbe essere ancora nella cache delle istruzioni quando viene chiamata da una posizione diversa e il predittore di diramazione potrebbe già essere riscaldato. Ci sono alcuni modelli che migliorano sempre l'efficienza, quindi non fa mai male usarli. L'allineamento non è uno di questi. Di solito non ha alcun effetto sulle prestazioni, a volte aiuta, a volte fa male. Seguo il mio consiglio: profilo prima, poi in linea.
dmazzoni,

5

Il modo migliore per scoprirlo è creare un profilo del programma e contrassegnare piccole funzioni che vengono chiamate molte volte e che bruciano attraverso i cicli della CPU inline. La parola chiave qui è "piccola" - una volta che l'overhead della chiamata di funzione è trascurabile rispetto al tempo trascorso nella funzione, è inutile incorporarli.

L'altro uso che suggerirei è che se hai piccole funzioni che vengono chiamate nel codice critico per le prestazioni abbastanza spesso da rendere rilevante una cache, dovresti probabilmente incorporare anche quelle. Ancora una volta, è qualcosa che il profiler dovrebbe essere in grado di dirti.


4

L'ottimizzazione prematura è la radice di tutti i mali!

Come regola generale, di solito inserisco solo "getter" e "setter". Una volta che il codice funziona ed è stabile, la profilazione può mostrare quali funzioni potrebbero trarre vantaggio dall'inline.

D'altra parte, i compilatori più moderni hanno algoritmi di ottimizzazione abbastanza buoni e spiegheranno cosa avresti dovuto fare per te.

Riassumendo: scrivi le funzioni di una riga in linea e preoccupati per gli altri in seguito.


2

Le funzioni incorporate potrebbero migliorare le prestazioni del codice eliminando la necessità di inserire argomenti nello stack. se la funzione in questione si trova in una parte critica del codice, dovresti prendere la decisione inline non inline nella parte di ottimizzazione del tuo progetto,

puoi leggere di più sugli inline nella faq di c ++


1

Uso spesso le funzioni incorporate non come ottimizzazione ma per rendere il codice più leggibile. A volte il codice stesso è più breve e più facile da comprendere rispetto a commenti, nomi descrittivi ecc. Ad esempio:

void IncreaseCount() { freeInstancesCnt++; }

Il lettore conosce immediatamente la semantica completa del codice.


0

In genere seguo una regola del pollice in cui eseguo una funzione con 3-4 semplici istruzioni come in linea. Ma è bene ricordare che è solo un suggerimento per il compilatore. L'ultima chiamata per renderla in linea o no viene presa solo dal compilatore. Se ci sono più di queste molte dichiarazioni, non dichiarerò in linea poiché con uno stupido compilatore potrebbe portare a un gonfiamento del codice.


0

Il modo migliore sarebbe quello di esaminare e confrontare le istruzioni generate per inline e non inline. Tuttavia, è sempre sicuro omettere inline. L'uso inlinepotrebbe portare a problemi che non vuoi.


0

Quando decido se utilizzare in linea, di solito tengo presente la seguente idea: sulle macchine moderne la latenza della memoria può essere un collo di bottiglia maggiore rispetto ai calcoli grezzi. È noto che le funzioni di allineamento chiamate spesso aumentano la dimensione eseguibile. Inoltre, una tale funzione potrebbe essere memorizzata nella cache del codice della CPU, il che ridurrà il numero di errori della cache quando è necessario accedere a quel codice.

Quindi, devi decidere tu stesso: l'allineamento aumenta o diminuisce la dimensione del codice macchina generato? Quanto è probabile che la chiamata alla funzione provochi un errore nella cache? Se è presente in tutto il codice, direi che la probabilità è alta. Se è limitato a un singolo loop stretto, la probabilità è probabilmente bassa.

In genere uso inline nei casi che elenco qui sotto. Tuttavia, se sei veramente preoccupato per le prestazioni, la profilazione è essenziale. Inoltre, potresti voler verificare se il compilatore prende effettivamente il suggerimento.

  • Routine brevi che vengono chiamate in un ciclo stretto.
  • Accessori di base (get / set) e funzioni wrapper.
  • Purtroppo, il codice modello nei file di intestazione ottiene automaticamente il suggerimento in linea.
  • Codice funzione utilizzato come una macro. (Ad esempio min () / max ())
  • Brevi routine matematiche.

0

Inoltre, un metodo inline ha gravi effetti collaterali quando si mantengono progetti di grandi dimensioni. Quando il codice inline viene modificato, tutti i file che lo usano verranno ricostruiti automaticamente dal compilatore (è un buon compilatore). Ciò potrebbe far perdere molto tempo allo sviluppo.

Quando un inlinemetodo viene trasferito in un file sorgente e non è più integrato, l'intero progetto deve essere ricostruito (almeno questa è stata la mia esperienza). E anche quando i metodi vengono convertiti in linea.


1
È un problema diverso. Si ottiene il problema di ricostruzione per il codice inserito in un file di intestazione. Indipendentemente dal fatto che sia contrassegnato inlineo meno (a meno che senza la inlineparola chiave, otterrai errori del linker - ma la inlineparola chiave non è il problema che causa eccessive ricostruzioni.
jalf

Tuttavia, la modifica di un metodo inline causerà build eccessive rispetto alla modifica di un metodo non inline in un file shource.
Thomas Matthews,

0

Si dovrebbe usare il qualificatore di funzione inline solo quando il codice della funzione è piccolo. Se le funzioni sono più grandi, si dovrebbero preferire le normali funzioni poiché il risparmio nello spazio di memoria vale il sacrificio relativamente piccolo nella velocità di esecuzione.


0

Quando pensi che il tuo codice sia abbastanza piccolo da essere usato come in linea e ricordi che la funzione in linea duplica il tuo codice e incollalo dove viene chiamata la funzione, quindi potrebbe essere abbastanza buono per aumentare il tempo di esecuzione ma anche un maggiore consumo di memoria. Non è possibile utilizzare la funzione in linea quando si utilizza una funzione loop / variabile statica / ricorsiva / switch / goto / Virtual. Virtual significa attendere fino al runtime e inline significa durante la compilazione, quindi non possono essere utilizzati contemporaneamente.


-2

Ho letto alcune risposte e ho visto che mancavano alcune cose.

La regola che uso non è quella di utilizzare in linea, a meno che non voglia che sia in linea. Sembra sciocco, ora spiegazione.

I compilatori sono abbastanza intelligenti e le funzioni brevi rendono sempre in linea. E non fa mai funzionare a lungo come in linea, a meno che il programmatore non abbia detto di farlo.

So che inline è un suggerimento o una richiesta al compilatore

In realtà inlineè un ordine per il compilatore, non ha scelta e dopo la inlineparola chiave rende tutto il codice in linea. Quindi non puoi mai usare la inlineparola chiave e il compilatore progetterà il codice più breve.

Quindi quando usare inline?

Da utilizzare se si desidera avere un po 'di codice in linea. Conosco solo un esempio, perché lo uso solo in una situazione. È l'autenticazione dell'utente.

Ad esempio ho questa funzione:

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

Non importa quanto sia grande questa funzione, voglio averla come in linea perché rende il mio software più difficile da decifrare.


2
inline è ancora un suggerimento. Il compilatore non può essere inline se ritiene che la tua funzione sia troppo gonfia.
È il

Uno dice che in linea è un ordine ... l'altro dice che è un suggerimento Qualcuno confermerebbe la sua affermazione in modo da poter determinare quale è vero?

@utente2918461, sostengo che la dichiarazione in linea sia solo un suggerimento. Questo è stato supportato da molti siti Web e libri
WARhead,
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.