2009-02-24 4 views
15

Alcune persone adorano usare la parola chiave inline in C e inserire funzioni grandi nelle intestazioni . Quando lo consideri inefficace? Lo considero a volte persino noioso, perché è inusuale.Quando è "in linea" inefficace? (in C)

Il mio principio è che inline deve essere utilizzato per piccole funzioni a cui si accede molto frequentemente o per avere un vero controllo del tipo. Comunque, i miei gusti mi guidano, ma non sono sicuro di come spiegare meglio i motivi per cui lo inline non è così utile per le grandi funzioni.

In this question le persone suggeriscono che il compilatore può fare un lavoro migliore a indovinare la cosa giusta da fare. Questa era anche la mia ipotesi. Quando provo a usare questo argomento, le persone rispondono che non funziona con funzioni provenienti da oggetti diversi. Beh, non lo so (per esempio, usando GCC).

Grazie per le vostre risposte!

+0

Sospetto che proprio come "registro" sia diventato obsoleto alla fine degli anni '80, "in linea" è obsoleto da diversi anni. –

+0

\ o /, muori "in linea"! – elmarco

+0

Inline non è sicuramente obsoleto. Hai mai letto il codice sorgente per un kernel moderno? Inline è irrilevante per le persone che scrivono applicazioni, ma quelle persone non dovrebbero usare C al giorno d'oggi in ogni caso, ed è altrettanto rilevante per la programmazione dei sistemi come sempre. – nosatalian

risposta

26

inline fa due cose:

  1. ti dà una deroga alla "una regola definizione" (vedi sotto). Questo si applica sempre a.
  2. Fornisce al compilatore un suggerimento per evitare una chiamata di funzione. Il compilatore è libero di ignorarlo.

# 1 Può essere molto utile (ad esempio, inserire la definizione nell'intestazione se breve) anche se # 2 è disabilitato.

In pratica i compilatori eseguono spesso un lavoro migliore per capire cosa incorporare da soli (specialmente se è disponibile l'ottimizzazione guidata dal profilo).


[EDIT: riferimenti completi e rilevanti del testo]

I due punti di cui sopra entrambi seguono dallo standard ISO/ANSI (ISO/IEC 9899: 1999 (E), comunemente noto come "C99") .

In §6.9 "Definizione esterno", comma 5:

Un definizione esterna è una dichiarazione esterna che è anche una definizione di una funzione (diverso da una definizione di linea) o di un oggetto. Se un identificatore dichiarato con collegamento esterno viene utilizzato in un'espressione (diversa da come parte dell'operando di un operatore sizeof il cui risultato è una costante intera), da qualche parte nell'intero programma ci deve essere esattamente una definizione esterna per l'identificatore; altrimenti, non ci deve essere più di uno.

Mentre la definizione equalivalente in C++ viene esplicitamente denominata One Definition Rule (ODR) serve allo stesso scopo. Gli esterni (vale a dire non "statici", e quindi locali per una singola unità di traduzione - tipicamente un singolo file sorgente) possono essere definiti solo una volta a meno che sia una funzione e in linea.

In §6.7.4, "Funzione specificatori", la parola chiave inline è definito:

facendo una funzione una funzione inline suggerisce che chiama alla funzione essere veloce possibile. [118] La misura in cui tali suggerimenti sono efficaci è definita dall'implementazione .

E nota (non normativo), ma fornisce chiarimenti:

Utilizzando, ad esempio, in alternativa al meccanismo di chiamata di funzione usuale, come ‘‘linea di sostituzione’’. La sostituzione in linea non è una sostituzione testuale, né crea una nuova funzione. Pertanto, ad esempio, l'espansione di una macro utilizzata all'interno del corpo della funzione utilizza la definizione che aveva nel punto in cui appare il corpo della funzione e non dove viene chiamata la funzione; e gli identificatori si riferiscono alle dichiarazioni di portata in cui si trova il corpo. Allo stesso modo, la funzione ha un unico indirizzo, indipendentemente dal numero di definizioni in linea che si verificano in aggiunta alla definizione esterna.

Riepilogo: ciò che la maggior parte degli utenti di C e C++ si aspettano da in linea non è quello che ottengono. Il suo apparente scopo primario, per evitare l'overhead di chiamata funzionale, è completamente opzionale. Ma per consentire la compilazione separata, è necessario un allentamento della definizione singola.

(Tutti enfasi nelle citazioni dallo standard.)


EDIT 2: Alcune note:

  • Ci sono varie restrizioni sulle funzioni inline esterni. Non è possibile avere una variabile statica nella funzione e non è possibile fare riferimento a oggetti/funzioni dell'oscilloscopio TU statico.
  • Appena visto su VC++ "whole program optimisation", che è un esempio di un compilatore che fa la propria cosa inline, piuttosto che l'autore.
+0

Non ho nemmeno pensato al n. 1, ma hai ragione - molto utile! Grazie per il consiglio. – Mike

+0

-1: # 1 non è vero - a volte per gcc, devi anche aggiungere 'static'! – Christoph

+1

@Christoph: questo è solo gcc. Appena controllato nel documento standard C99. – Richard

1
  1. inline funge solo un accenno.
  2. Aggiunto solo di recente. Quindi funziona solo con i compilatori conformi agli standard più recenti.
+0

Aggiunto di recente?Sono abbastanza sicuro che la linea sia stata in circolazione per almeno una dozzina di anni (da quando ho iniziato a scrivere in C) –

+0

inline è stato in C++ per molto tempo. In C è stato nello standard dal C99 (ma non molti compilatori supportano completamente C99), ma è stato in vari compilatori come un'estensione per un po '. Quindi per i programmi C, l'uso di inline può essere un po 'un mal di testa della portabilità. –

+0

10 anni. Eppure ottenere MS per implementare un compilatore C solo decente. Comunque, è recente in C-time;) Aggiunto in C99. Vedi: http://en.wikipedia.org/wiki/C99 – dirkgently

4

Inline è inefficace quando si utilizza il puntatore per funzionare.

+0

certo, questo non è il caso nel mio progetto comunque. grazie – elmarco

+0

+1 osservazione interessante.! –

5

La cosa importante di una dichiarazione in linea è che non fa necessariamente nulla. Un compilatore è libero di decidere, in molti casi, di incorporare una funzione non dichiarata in tal modo e di collegare funzioni dichiarate in linea.

+0

anche se la funzione inline a cui si fa il collegamento proviene da un diverso oggetto .o? – elmarco

+0

il modificatore in linea è solo un suggerimento. Se il compilatore sceglie, può decidere che una funzione non può essere inline, ma il linker potrebbe quindi decidere di riscrivere comunque la chiamata di funzione come inline. Non c'è solo garanzia di comportamento con inline. – SingleNegationElimination

2

Proprio così. L'utilizzo in linea di grandi funzioni aumenta il tempo di compilazione e offre poche prestazioni extra all'applicazione. Le funzioni inline sono usate per dire al compilatore che una funzione deve essere inclusa senza una chiamata, e tale dovrebbe essere un piccolo codice ripetuto molte volte. In altre parole: per le grandi funzioni, il costo di effettuare la chiamata rispetto al costo della propria implementazione è trascurabile.

+0

Come accennato in precedenza, inline viene utilizzato per _suggest_ al compilatore che la funzione deve essere inclusa senza una chiamata. Non c'è alcuna garanzia che lo farà davvero. – Peter

2

Inline può essere utilizzato per funzioni piccole e usate frequentemente come il metodo getter o setter. Per le grandi funzioni non è consigliabile utilizzare in linea poiché aumenta la dimensione di exe. Anche per le funzioni ricorsive, anche se si fa inline, il compilatore lo ignorerà.

2

Principalmente uso le funzioni inline come macro typesafe. Si è parlato di aggiungere il supporto per le ottimizzazioni link-time a GCC per un bel po 'di tempo, specialmente da quando LLVM è arrivato. Non so quanto sia stato effettivamente implementato, comunque.

3

Inline è efficace in un caso: quando si verifica un problema di prestazioni, è stato eseguito il profiler con dati reali e si è rilevato che l'overhead di chiamata di funzione per alcune piccole funzioni è significativo.

Al di fuori di questo, non riesco a immaginare perché lo useresti.

+0

Dang. Ho ingannato la tua risposta. Bene, solo questo fatto dimostra chiaramente che stavi impartendo una grande intuizione, così in alto. :-) –

+0

Direi che a volte non è necessario eseguire il profiler per sapere che l'overhead è significativo. Ad esempio, è abbastanza ovvio per me che una funzione di incremento atomico dovrebbe essere in linea e ridurrà anche la dimensione del codice. –

2

Personalmente non penso che dovresti mai in linea, a meno che tu non abbia prima eseguito un profiler sul tuo codice e abbia dimostrato che ci sia un collo di bottiglia significativo su quella routine che può essere parzialmente alleviato da inlining.

Questo è l'ennesimo caso dell'ottimizzazione prematura su cui Knuth ha messo in guardia.

5

Un esempio per illustrare i vantaggi di inline. sinCos.h:

int16 sinLUT[ TWO_PI ]; 

static inline int16_t cos_LUT(int16_t x) { 
    return sin_LUT(x + PI_OVER_TWO) 
} 

static inline int16_t sin_LUT(int16_t x) { 
    return sinLUT[(uint16_t)x]; 
} 

Quando si esegue un numero pesante scricchiolio e si vuole evitare di sprecare cicli sul calcolo sin/cos si sostituisce sin/cos con una LUT.

Quando si compila, senza linea il compilatore non ottimizza il ciclo e l'Asm di uscita mostrerà qualcosa sulla falsariga di:

;*----------------------------------------------------------------------------* 
;* SOFTWARE PIPELINE INFORMATION 
;*  Disqualified loop: Loop contains a call 
;*----------------------------------------------------------------------------* 

Quando si compila con il compilatore in linea ha la conoscenza di ciò che accade nel ciclo e ottimizzerà perché sa esattamente cosa sta succedendo.

L'output .asm avrà un ciclo ottimizzato "pipelined" (vale a dire cercherà di utilizzare completamente tutte le ALU del processore e tenterà di mantenere la pipeline del processore completa senza NOPS).


In questo caso specifico, sono stato in grado di aumentare il mio rendimento di circa il 2X o 4X, che mi ha fatto all'interno di quello che mi serviva per il mio termine in tempo reale.


p.s. Stavo lavorando su un processore a punto fisso ... e qualsiasi operazione in virgola mobile come sin/cos ha ucciso la mia performance.

5

Un altro motivo per cui non è consigliabile utilizzare inline per funzioni di grandi dimensioni, è nel caso delle librerie. Ogni volta che si modificano le funzioni inline, si potrebbe perdere la compatibilità ABI perché l'applicazione compilata su un'intestazione precedente ha ancora in linea la versione precedente della funzione. Se le funzioni inline vengono utilizzate come macro typesafe, è molto probabile che la funzione non debba mai essere modificata nel ciclo di vita della libreria. Ma per le grandi funzioni questo è difficile da garantire.

Ovviamente, questo argomento si applica solo se la funzione fa parte dell'API pubblica.

1

Le funzioni inline devono essere di circa 10 righe o meno, dare o prendere, a seconda del compilatore di scelta.

Puoi dire al tuo compilatore che vuoi qualcosa di inline .. è compito del compilatore farlo. Non esiste alcuna opzione in-force-in-line che io sappia di quali il compilatore non può ignorare.Questo è il motivo per cui dovresti guardare l'output dell'assembler e vedere se il tuo compilatore in realtà ha eseguito la funzione in linea, in caso contrario, perché no? Molti compilatori dicono in silenzio "ti frego!" a questo riguardo.

quindi se:

linea statica unsigned int foo (const char * bar)

.. non migliora le cose oltre int foo statico() è il momento di rivedere le vostre ottimizzazioni (e probabilmente loop) o discutere con il tuo compilatore. Fai particolare attenzione a discutere prima con il tuo compilatore, non con le persone che lo sviluppano .. o il tuo appena in serbo per molte letture sgradevoli quando apri la tua casella di posta elettronica il giorno successivo.

Nel frattempo, quando si crea qualcosa (o si tenta di creare qualcosa) in linea, ciò che giustifica effettivamente fa gonfiare? Vuoi davvero che quella funzione venga espansa ogni volta che viene richiamata la data? Il salto è così costoso ?, il tuo compilatore di solito è corretto 9/10 volte, controlla l'output intermedio (o asm dump).