Perché i compilatori non fanno tutto in linea? [chiuso]

9

A volte i compilatori eseguono chiamate in linea. Ciò significa che spostano il codice della funzione chiamata nella funzione di chiamata. Questo rende le cose un po 'più veloci perché non c'è bisogno di spingere e far saltare le cose dentro e fuori dallo stack delle chiamate.

Quindi la mia domanda è: perché i compilatori non fanno tutto in linea? Presumo che renderebbe l'eseguibile notevolmente più veloce.

L'unica ragione per cui riesco a pensare è un eseguibile significativamente più grande, ma è veramente importante in questi giorni con centinaia di GB di memoria? Non vale la prestazione migliorata?

C'è qualche altra ragione per cui i compilatori non si limitano a inserire tutte le chiamate di funzione?

    
posta Aviv Cohn 28.08.2014 - 15:33
fonte

6 risposte

21

Innanzitutto, un importante effetto di inline è che consente ulteriori ottimizzazioni sul sito di chiamata.

Per la tua domanda: ci sono cose che sono difficili o addirittura impossibili da allineare:

  • librerie collegate dinamicamente

  • funzioni dinamicamente determinate (invio dinamico, chiamate attraverso i puntatori di funzioni)

  • funzioni ricorsive (coda ricorsione può)

  • funzioni per le quali non hai il codice (ma l'ottimizzazione del tempo di collegamento lo consente per alcune di esse)

Quindi l'inlining non ha solo effetti benefici:

  • eseguibile più grande significa più spazio sul disco e tempo di caricamento maggiore

  • più grande eseguibile significa aumento della pressione della cache (si noti che l'inlining di funzioni abbastanza piccole come getter semplici può ridurre la dimensione dell'eseguibile e la pressione della cache)

E infine, per le funzioni che richiedono un tempo non banale da eseguire, il guadagno non vale la pena.

    
risposta data 28.08.2014 - 15:40
fonte
10

Un limite importante è il polimorfismo di runtime. Se si verifica una spedizione dinamica quando si scrive foo.bar() , è impossibile in linea la chiamata al metodo. Questo spiega perché i compilatori non fanno tutto in linea.

Le chiamate ricorsive non possono essere facilmente delineate neanche.

Anche l'inlining dei moduli incrociati è difficile da eseguire per motivi tecnici (la ricompilazione incrementale sarebbe impossibile per ex)

Tuttavia, i compilatori fanno molte cose in linea.

    
risposta data 28.08.2014 - 15:39
fonte
6

Per prima cosa, non puoi sempre essere in linea, ad es. le funzioni ricorsive potrebbero non essere sempre inlinabili (ma un programma contenente una definizione ricorsiva di fact con solo una stampa di fact(8) potrebbe essere inline).

Quindi, l'inlining non è sempre vantaggioso. Se il compilatore è così in linea che il codice del risultato è abbastanza grande da avere parti calde che non si adattano ad esempio la cache delle istruzioni L1, potrebbe essere molto più lenta della versione non inline (che si adatterebbe facilmente alla cache L1) ... Inoltre, i processori recenti sono molto veloci nell'eseguire un'istruzione macchina CALL (almeno in una posizione nota , cioè una chiamata diretta, non una chiamata attraverso il puntatore).

Finalmente, l'inlining completo richiede un'intera analisi del programma. Questo potrebbe non essere possibile (o è troppo costoso). Con C o C ++ compilato da GCC (e anche con Clang / LLVM ) devi abilitare l'opzione ottimizzazione del tempo di collegamento (compilando e collegando, ad esempio, con g++ -flto -O2 ) e richiede un bel po 'di tempo di compilazione.

    
risposta data 28.08.2014 - 17:25
fonte
4

Per quanto sorprendente possa sembrare, l'inlining di tutto ciò non riduce necessariamente i tempi di esecuzione. L'aumento della dimensione del codice può rendere difficile per la CPU mantenere tutto il codice nella sua cache in una volta. Una perdita di cache sul codice diventa più probabile e una perdita di cache è costosa. Ciò è molto peggiore se le tue funzioni potenzialmente integrate sono grandi.

Di tanto in tanto ho notato miglioramenti delle prestazioni notevoli prendendo grandi blocchi di codice contrassegnati come "in linea" dai file di intestazione, inserendoli nel codice sorgente, in modo che il codice sia solo in un posto piuttosto che a ogni chiamata posto. Quindi la cache della CPU viene utilizzata meglio e si ottiene anche un tempo di compilazione migliore ...

    
risposta data 28.08.2014 - 18:46
fonte
1

Inlining di tutto ciò non significa solo un aumento del consumo di memoria del disco, ma anche un aumento del consumo di memoria interna che non è così abbondante. Ricorda che il codice si basa anche sulla memoria nel segmento di codice; se una funzione viene chiamata da 10000 posti (ad esempio quelli delle librerie standard in un progetto abbastanza grande), il codice per quella funzione occupa una memoria interna di 10000 volte più.

Un altro motivo potrebbe essere il compilatore JIT; se tutto è in linea, non ci sono punti caldi da compilare dinamicamente.

    
risposta data 28.08.2014 - 17:23
fonte
1

Uno, ci sono degli esempi semplici in cui l'inlining di tutto funzionerà molto male. Considera questo semplice codice C:

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

Indovina cosa ti farà capire tutto ciò che farà.

Successivamente, si presuppone che l'inlining renderà le cose più veloci. Talvolta succede, ma non sempre. Una ragione è che il codice che si inserisce nella cache delle istruzioni è molto più veloce. Se chiamo una funzione da 10 posizioni, eseguirò sempre il codice che si trova nella cache delle istruzioni. Se è in linea, le copie sono dappertutto e funzionano molto più lentamente.

Ci sono altri problemi: Inlining produce enormi funzioni. Le enormi funzioni sono molto più difficili da ottimizzare. Ho notevoli vantaggi nel codice critico delle prestazioni nascondendo le funzioni in un file separato per impedire al compilatore di inlining. Di conseguenza, il codice generato per queste funzioni era molto meglio quando erano nascosti.

A proposito. Non ho "centinaia di GB di memoria". Il mio computer di lavoro non ha nemmeno "centinaia di GB di spazio su disco". E se la mia applicazione in cui "centinaia di GB di memoria", ci vorrebbero 20 minuti solo per caricare l'applicazione in memoria.

    
risposta data 28.08.2014 - 19:34
fonte

Leggi altre domande sui tag