2010-02-01 3 views
26

Personalmente, mi piace molto librerie intestazione sola, ma ci sono rivendicazioni che provocano pesantezza del codice a causa di sovra-inlining (così come l'altro problema evidente di compilazione più volte).Quando sono accettabili le librerie di sola intestazione?

Mi chiedevo, quanta verità c'è da queste affermazioni (quello sulla pesantezza)?

Inoltre, sono i costi 'giustificato'? (Ovviamente ci sono casi inevitabili come quando è una libreria implementata puramente o principalmente con modelli, tuttavia sono più interessato al caso in cui sia effettivamente disponibile una scelta.)

So che non esiste una regola rigida e veloce, linea guida , ecc. per quanto riguarda cose come questa, ma sto solo cercando di capire cosa pensano gli altri in merito.

P.S. Sì, questa è una domanda molto vaga e soggettiva, ne sono a conoscenza, e quindi l'ho etichettata come tale.

risposta

6

Io lavoro per una società che ha un reparto "Middleware" proprio per mantenere un paio di centinaia di librerie che vengono comunemente utilizzati da un gran numero di squadre.

Pur essendo nella stessa azienda, si teme di intestazione unico approccio e preferiscono favorire compability binario sulle prestazioni a causa della facilità di manutenzione.

Il consenso generale è che il guadagno di performance (se presente) non varrebbe la pena.

Inoltre, il cosiddetto "codice-gonfiare" può avere un impatto negativo sulle prestazioni più codice da caricare nella cache implica più di cache miss, e questi sono gli assassini di prestazioni.

In un mondo ideale Suppongo che il compilatore e il linker potrebbe essere abbastanza intelligente non generare tali norme "più definizioni", ma fino a quando non è il caso, io (personalmente) favorevoli:

  • compatibilità binaria
  • non inlining (per i metodi che sono più di un paio di righe)

Perché non si prova? Preparare le due librerie (una solo intestazione e l'altra senza metodi di allineamento su un paio di righe) e controllare le rispettive prestazioni nel TUO caso.

EDIT:

E 'stato sottolineato da 'JALF'(grazie) che dovrei preciso quello che volevo dire esattamente dalla compatibilità binaria.

2 versioni di una data libreria si dice compatibile binario se è possibile (di solito) Link contro l'uno o l'altro senza alcun cambiamento della propria libreria.

Poiché è possibile collegare soltanto con una versione di una data libreria Target, tutte le librerie caricate che l'uso Target utilizzerà effettivamente la stessa versione ... e qui è la causa della transitività di questa proprietà.

MyLib --> Lib1 (v1), Lib2 (v1) 
Lib1 (v1) --> Target (v1) 
Lib2 (v1) --> Target (v1) 

Ora, dicono che abbiamo bisogno di una correzione in Target per una funzione utilizzata solo da Lib2, forniamo una nuova versione (v2).Se (v2) è compatibile a livello binario con (v1), allora possiamo fare:

Lib1 (v1) --> Target (v2) 
Lib2 (v1) --> Target (v2) 

Tuttavia, se non è il caso, allora avremo:

Lib1 (v2) --> Target (v2) 
Lib2 (v2) --> Target (v2) 

Sì, avete letto bene, anche se Lib1 fatto Non è richiesta la correzione, è necessario ricostruirla con una nuova versione di Target poiché questa versione è obbligatoria per l'aggiornamento Lib2 e Executable è possibile collegarsi solo a una versione di Target.

Con una libreria di sola intestazione, poiché non si dispone di una libreria, non si è effettivamente compatibili binari. Quindi ogni volta che fai qualche correzione (sicurezza, bug critico, ecc ...) devi consegnare una nuova versione e tutte le librerie che dipendono da te (anche indirettamente) dovranno essere ricostruite contro questa nuova versione!

+3

Come funziona solo intestazione librerie implica pesantezza del codice? Generalmente il compilatore non sarà in linea se significa codice significativamente più grande. E, del resto, in che modo la compatibilità binaria è influenzata dalle librerie di sola intestazione? – jalf

+0

@Matthieu: I test sono in effetti sempre buoni per determinare quale darebbe i risultati migliori. – DrYak

+1

@Jalf: *** 1. Scenario di intestazione: per ogni singola modifica alla libreria un aggiornamento alla libreria significa ricompilare ogni ultimo progetto che ha dipendenza da esso. (Pensa alla situazione di Matthieu: centinaia di librerie, un sacco di squadre, molte ricompense coinvolte). *** 2. scenario solo binario: per molte modifiche diverse, un aggiornamento comporterà semplicemente il rilascio di un nuovo file .SO o .DLL, a condizione che l'ABI rimanga lo stesso. Sebbene la nuova funzionalità possa modificare le firme dei metodi e le strutture di dati, gli aggiornamenti critici (sicurezza o stabilità) raramente. – DrYak

1

L'over-inlining è probabilmente qualcosa che deve essere affrontato dal chiamante, sintonizzando le opzioni del compilatore, piuttosto che dal destinatario che cerca di controllarlo attraverso gli strumenti smussati della parola chiave inline e le definizioni nelle intestazioni. Ad esempio GCC ha -finline-limit e amici, quindi puoi utilizzare diverse regole di inlining per le diverse unità di traduzione. Ciò che è troppo complesso per te potrebbe non essere troppo esplicativo per me, a seconda dell'architettura, delle dimensioni e della velocità della cache delle istruzioni, di come viene utilizzata la funzione, ecc. Non che io abbia mai avuto bisogno di fare questo tuning: in pratica quando ha valeva la pena di preoccuparsi di ciò, è valsa la pena riscriverlo, ma potrebbe essere una coincidenza. Ad ogni modo, se io sono l'utente di una libreria, a parità di tutti gli altri preferisco avere l'opzione di inline (soggetto al mio compilatore, e che potrei non occupare) di non essere in grado di inline.

Penso che il terrore del codice gonfia dalle librerie di intestazione provenga più dalla preoccupazione che il linker non sia in grado di rimuovere le copie ridondanti del codice. Quindi, indipendentemente dal fatto che la funzione sia effettivamente delineata nei siti di chiamata o meno, la preoccupazione è che si finisce con una copia richiamabile della funzione (o classe) per file oggetto che la usa. Non riesco a ricordare se gli indirizzi presi per le funzioni inline in diverse unità di traduzione in C++ devono essere uguali, ma anche supponendo che lo facciano, così che c'è una copia "canonica" della funzione nel codice collegato, non necessariamente significa che il linker rimuoverà effettivamente le funzioni di duplicazione morte. Se la funzione è definita in una sola unità di traduzione, puoi essere ragionevolmente sicuro che ci sarà una sola copia indipendente per libreria statica o eseguibile che la usa.

Onestamente non so quanto sia ben radicata questa paura. Tutto ciò su cui ho lavorato è stato talmente limitato dalla memoria che abbiamo usato le funzioni così piccole che non abbiamo mai visto che la versione inline sia notevolmente più grande del la mente si duplica, oppure è così limitata che non ci interessa alcun duplicato da nessuna parte. Non ho mai trovato la via di mezzo per cercare e contare i duplicati su vari compilatori diversi. Ho sentito occasionalmente da altri che è stato un problema con il codice di modello, quindi, quindi credo che ci sia la verità nelle affermazioni.

Rendendo questo come ora, penso che se si spedisce una libreria di sola intestazione, l'utente può sempre pasticciarlo se non gli piace. Scrivi una nuova intestazione che dichiara tutte le funzioni e una nuova unità di traduzione che include le definizioni.Funzioni definite in classi dovranno essere spostati alle definizioni esterni, quindi se si vuole sostenere questo uso senza richiedere all'utente di sborsare il codice, si potrebbe evitare di fare questo e la fornitura di due intestazioni:

// declare.h 
inline int myfunc(int); 

class myclass { 
    inline int mymemberfunc(int); 
}; 

// define.h 
#include "declare.h" 
int myfunc(int a) { return a; } 

int myclass::mymemberfunc(int a) { return myfunc(a); } 

chiamanti che sono preoccupati per il codice gonfiare probabilmente può superare in astuzia loro compilatore includendo declare.h in tutti i loro file, quindi scrivendo:

// define.cpp 
#include "define.h" 

probabilmente anche bisogno di evitare l'ottimizzazione dell'intero programma per essere sicuri che il codice non sarà inline, ma allora non puoi essere sicuro che anche una funzione non in linea non verrà evidenziata dall'ottimizzazione dell'intero programma.

I chiamanti che non sono preoccupati per il codice gonfio possono usare define.h in tutti i loro file.

6

Nella mia esperienza troppo grosso non è stato un problema:

  • Solo intestazione librerie dare compilatori maggiore capacità di linea, ma non forzare compilatori inline - molti compilatori trattano la parola chiave inline come nient'altro di un comando per ignorare più definizioni identiche.

  • I compilatori di solito dispongono di opzioni da ottimizzare per controllare la quantità di inlining;/OS sui compilatori Microsoft.

  • Di solito è meglio consentire al compilatore di gestire i problemi di velocità e di dimensioni. Vedrai solo gonfiare le chiamate che sono state effettivamente delineate, e il compilatore le indicherà solo se la sua euristica indica che l'inlining migliorerà le prestazioni.

che non mi dispiacerebbe codice gonfiare come un motivo per stare lontano da solo intestazione biblioteche - ma io vi invito a considerare quanto un colpo di testa unico approccio aumenterà i tempi di compilazione da parte.

3

Sono d'accordo, le librerie incorporate sono molto più facili da consumare.

In ingrossamento dipende in gran parte dalla piattaforma di sviluppo con cui si sta lavorando, in particolare dalle funzionalità del compilatore/linker. Non mi aspetterei che fosse un grosso problema con VC9 tranne in alcuni casi d'angolo.

Ho visto alcuni cambiamenti notevoli nelle dimensioni finali in alcuni punti di un grande progetto VC6, ma è difficile dare uno specifico "accettabile, se ...". Probabilmente hai bisogno di provare con il tuo codice nel tuo diavolo.

Un secondo problema potrebbe essere la compilazione dei tempi, anche quando si utilizza l'intestazione precompilata (ci sono anche dei compromessi).

In terzo luogo, alcuni costrutti sono problematici - ad es. membri di dati statici condivisi tra unità di traduzione - o evitando di avere un'istanza separata in ogni unità di traduzione.


ho visto il seguente meccanismo per dare all'utente una scelta:

// foo.h 
#ifdef MYLIB_USE_INLINE_HEADER 
#define MYLIB_INLINE inline 
#else 
#define MYLIB_INLINE 
#endif 

void Foo(); // a gazillion of declarations 

#ifdef MYLIB_USE_INLINE_HEADER 
#include "foo.cpp" 
#endif 

// foo.cpp 
#include "foo.h" 
MYLIB_INLINE void Foo() { ... }