Domanda

Ho un tic psicologico che mi rende riluttanti a utilizzare librerie di grandi dimensioni (come GLib Boost ) in linguaggi di livello inferiore come C e C ++. Nella mia mente, credo che:

  

Bene, questa libreria ha migliaia di   ore di lavoro messo in esso, ed è stato   creato da persone che sanno molto di più   sulla lingua che io mai.   I loro autori e fan dicono che   le librerie sono veloce e affidabile,   e la funzionalità sembra davvero   utile, e sarà certamente fermarmi   da (male) reinventare ruote.

     

Ma porca miseria, non sono mai intenzione di utilizzare   ogni funzione in quella biblioteca. Suo   troppo grande ed è probabilmente diventato gonfio   negli anni; è un'altra palla   e catena il mio programma ha bisogno di trascinare in giro.

Il Torvalds rant (discutibile se è) non è esattamente messo il mio cuore a proprio agio sia.

C'è una base per il mio pensiero, o sto semplicemente irragionevole e / o ignoranti? Anche se io uso solo una o due caratteristiche di una grande biblioteca, attraverso il collegamento con quella libreria faccio a incorrere in spese generali performance di runtime?

Sono sicuro che dipende troppo su quello che la biblioteca è specifica, ma sono generalmente interessati a sapere se grandi biblioteche saranno, a livello tecnico, di per sé introdurre inefficienze.

Sono stanco di ossessionante e borbottando e preoccuparsi di questo, quando non ho le conoscenze tecniche per sapere se ho ragione o no.

mi ha messo fuori dalla mia miseria!

È stato utile?

Soluzione

  

Anche se io uso solo una o due caratteristiche di una grande biblioteca, attraverso il collegamento con quella libreria faccio a incorrere in spese generali performance di runtime?

In generale, no.

Se la libreria in questione non ha un sacco di codice indipendente dalla posizione, allora ci sarà un costo di start-up, mentre il linker dinamico esegue delocalizzazioni sulla libreria quando è richiesto. Di solito, questo fa parte delle start-up del programma. Non v'è alcun effetto prestazioni di runtime di là di questo.

I linker sono anche bravi a rimuovere "codice morto" da librerie staticamente legati al momento della compilazione, in modo che qualsiasi librerie statiche che usate avrà in testa dimensione minima. Le prestazioni non ha nemmeno entrare in esso.

Francamente, si sta preoccuparsi delle cose sbagliate.

Altri suggerimenti

Non posso commentare su GLib, ma di tenere presente che un sacco di codice in Boost è solo intestazioni e dato il principio C ++ dell'utente pagare solo per quello che si sta utilizzando, le librerie sono abbastanza efficienti. Ci sono diverse librerie che richiedono di collegare contro di loro (regex, filesystem venire in mente) ma sono librerie separate. Con Boost non si colleghi contro una grande biblioteca monolitico, ma solo contro i componenti più piccoli che si fa usare.

Naturalmente, l'altra domanda è - qual è l'alternativa? Vuoi implementare la funzionalità che è in Boost te stesso quando ne avete bisogno? Dato che un sacco di persone molto competenti hanno lavorato su questo codice e assicurato che funziona attraverso una moltitudine di compilatori ed è ancora efficace, questo potrebbe non essere esattamente un'impresa semplice. Inoltre si sta scoprendo l'acqua calda, almeno in una certa misura. IMHO è possibile trascorrere questo tempo in modo più produttivo.

Boost non è una grande biblioteca.

Si tratta di una raccolta di molte piccole librerie. La maggior parte di loro sono così piccoli che stanno contenute in un'intestazione o due. Utilizzando boost::noncopyable non trascina boost::regex o boost::thread nel codice. Sono diverse biblioteche. Stanno solo distribuiti come parte della stessa collezione della biblioteca. Ma si paga solo per quelle che si usano.

Ma parlando in generale, perché esistono grandi librerie, anche se Boost non è uno di loro:

  

C'è una base per il mio pensiero, o sto semplicemente irragionevole e / o ignoranti? Anche se io uso solo una o due caratteristiche di una grande biblioteca, attraverso il collegamento con quella libreria faccio a incorrere in spese generali performance di runtime?

No base, più o meno . È possibile verificare voi stessi.

Scrivere un programma piccolo C ++ e compilarlo. Ora aggiungere una nuova funzione ad esso, uno che non è mai chiamato, ma è definito. Compilare nuovamente il programma. Supponendo ottimizzazioni sono abilitati, ottiene spogliato fuori dal linker, perché è inutilizzato. Quindi il costo di includere aggiuntivo non utilizzato il codice è zero.

Naturalmente ci sono delle eccezioni. Se il codice crea un'istanza di oggetti globali, quelli che non potrebbero essere rimossi (è per questo che con l'indicazione dell'header iostream aumenta la dimensione eseguibile), ma in generale, è possibile includere il maggior numero di intestazioni e link a tutte le librerie come ti piace, e ha vinto' t influisce sulle dimensioni, le prestazioni o l'utilizzo della memoria del vostro programma * finché non lo fai usare qualsiasi codice aggiunto.

Un'altra eccezione è che se si collega in modo dinamico a una DLL o .so, l'intera libreria deve essere distribuito, e quindi non può essere privato del codice non utilizzato. Ma librerie che vengono compilati staticamente nel vostro eseguibile (sia le librerie statiche (lib o .a) o semplicemente come file di include di solito possono essere tagliati giù dal linker, rimuovendo i simboli inutilizzati.

Grande biblioteca sarà, dal codice di vista delle prestazioni:

  • occupare più memoria , se ha un binario runtime (gran parte del boost non richiedono file binari di runtime, sono "header-only"). Mentre il sistema operativo caricherà solo le parti effettivamente utilizzate della biblioteca alla RAM, è ancora in grado di caricare più del necessario, perché la granularità di ciò che sta caricato è uguale alla dimensione della pagina (4 KB sul mio sistema, però).
  • richiedere più tempo per caricare di linker dinamico, se, ancora una volta, ha bisogno di file binari di runtime. Ogni volta che il programma viene caricato, linker dinamico deve corrispondere ogni funzione è necessario libreria esterna per contenere con il suo indirizzo reale in memoria. Ci vuole un po 'di tempo, ma solo un po' (tuttavia, è importante ad una scala di caricamento di molti programmi, come ad esempio l'avvio di ambiente desktop, ma non avere una scelta lì).

    E sì, ci vorrà un salto in più e un paio di aggiustamenti puntatore in fase di esecuzione ogni volta che si chiama funzione esterna di un condiviso (collegato dinamicamente) biblioteca

da dello sviluppatore di vista delle prestazioni:

  • aggiungere una dipendenza esterna . Sarete a seconda qualcun altro . Anche se il software gratuito che della biblioteca, è necessario spesa extra per modificarlo. Alcuni sviluppatori di mooolto programmi di basso livello (sto parlando di kernel del sistema operativo) odiano a fare affidamento su nessuno - che è il loro vantaggio professionale. Così le declamazioni.

    Tuttavia, questo può essere considerato un vantaggio. Se altre persone sono abituati a boost, troveranno concetti familiari e termini nel vostro programma e sarà più efficace comprensione e modificandolo.

  • librerie più grandi di solito contengono concetti specifici della biblioteca che richiedono tempo per capire. Considerare Qt. Contiene segnali e slot e infrastrutture moc legati. Rispetto alle dimensioni dell'intero Qt, loro l'apprendimento prende una piccola frazione di tempo. Ma se si utilizza una piccola parte di una grande biblioteca tale, che può essere un problema.

Codice in eccesso non magicamente far funzionare il processore più lento. Tutto ciò che fa è stare lì che occupa un po 'di memoria.

Se intendi collegare staticamente e il vostro linker è affatto ragionevole, allora include solo le funzioni che effettivamente utilizzati in ogni caso.

Il termine che mi piace per quadri, set di libreria, e alcuni tipi di strumenti di sviluppo, è tecnologie di piattaforma. tecnologie della piattaforma hanno costi al di là di impatto sulle dimensioni del codice e le prestazioni.

  1. Se il progetto è a sua volta destinato ad essere utilizzato come una biblioteca o di un quadro, si può finire per spingere la propria piattaforma scelte tecnologiche sugli sviluppatori che utilizzano la libreria.

  2. Se si distribuisce il progetto in forma di sorgenti si può finire per spingere le scelte tecnologiche piattaforma su utenti finali.

  3. Se non lo fai in modo statico collega tutti i tuoi quadri scelti e librerie, si può finire per gravare gli utenti finali con i problemi delle versioni di libreria.

  4. Compila tempo la produttività degli sviluppatori effetti. collegamento incrementale, intestazioni precompilate, una corretta gestione delle dipendenze di testa, ecc, può aiutare a gestire i tempi di compilazione, ma non eliminano i problemi di prestazioni del compilatore associati alle enormi quantità di codice inline alcune tecnologie di piattaforma introducono.

  5. Per i progetti che vengono distribuiti come sorgente, compilare il tempo colpisce gli utenti finali del progetto.

  6. Molte tecnologie di piattaforma hanno le proprie esigenze di sviluppo. Questi requisiti possono accumulano rendendo difficile e richiede tempo per i nuovi sviluppatori ad un progetto per essere in grado di replicare l'ambiente necessario per consentire la compilazione e il debug.

  7. Utilizzo di alcune tecnologie di piattaforma in effetti crea un nuovo linguaggio di programmazione per il progetto. Questo rende più difficile per i nuovi sviluppatori a contribuire.

Tutti i progetti hanno dipendenze tecnologia di piattaforma, ma per molti progetti ci sono benefici reali per mantenere queste dipendenze al minimo.

Ci può essere un piccolo overhead durante il caricamento di queste librerie se sono collegate in modo dinamico. Questo sarà tipicamente una piccola, piccola frazione del tempo il vostro programma di trascorre in esecuzione.

Tuttavia non ci sarà in testa una volta che tutto è stato caricato.

Se non si desidera utilizzare tutti di spinta, allora non farlo. E 'modulare, in modo da poter utilizzare le parti che si desidera e ignorare il resto.

Più grande non significa di per sé implica più lento. Contrariamente ad alcune delle altre risposte, non c'è alcuna differenza intrinseca tra le biblioteche memorizzati interamente in intestazioni e librerie memorizzate in file oggetto sia.

Header-solo le librerie possono avere un vantaggio indiretto. La maggior parte delle librerie basate su modelli devono essere solo intestazioni (o un sacco di codice finisce nelle intestazioni in ogni caso), e modelli fanno dare un sacco di opportunità di ottimizzazione. Prendendo il codice in una libreria tipico file oggetto e lo spostamento tutto in intestazioni sarà non , tuttavia, di solito hanno molti effetti buoni (e potrebbe portare alla pesantezza del codice).

La vera risposta per una particolare libreria di solito dipenderà dalla sua struttura complessiva. E 'facile pensare di "Boost", come qualcosa di enorme. In realtà, è una vasta collezione di biblioteche, la maggior parte dei quali sono individualmente abbastanza piccolo. Non si può dire molto (significato) su Boost nel suo complesso, in quanto le singole biblioteche sono scritti da persone diverse, con diverse tecniche, obiettivi, ecc Alcuni di loro (ad esempio Format, Assegna) in realtà sono più lento di quasi tutto si sarebbe molto probabilmente a fare da soli. Altri (ad esempio Pool) fornire cose che potreste fare da soli, ma probabilmente non lo farà, per ottenere almeno minori miglioramenti di velocità. Alcuni (ad esempio uBLAS) Usa modello magia resistente per correre più veloce di qualsiasi, ma una piccola percentuale di noi può sperare di raggiungere da soli.

Ci sono, naturalmente, un bel paio di librerie che in realtà sono individualmente grandi biblioteche. In non pochi casi, questi sono davvero più lento di quello che ci scrive da soli. In particolare, molti (la maggior parte?) Di loro tenta di essere molto più generale rispetto a quasi tutto ciò che si sarebbe affatto probabile di scrivere da soli. Anche se questo non fa necessariamente portare a codice più lento, c'è sicuramente un forte tendenza in quella direzione. Come con un sacco di altro codice, quando si sta sviluppando librerie in commercio, i clienti tendono ad essere molto più interessati a funzionalità rispetto cose come la dimensione della velocità.

Alcune librerie anche dedicano un sacco di spazio, il codice (e spesso almeno frammenti di tempo) per risolvere i problemi che possono benissimo non si preoccupano affatto. Solo per esempio, anni fa, ho usato una libreria di elaborazione delle immagini. Il suo sostegno per oltre 200 formati di immagine sembrava davvero impressionante (e in un certo senso è stato davvero), ma sono abbastanza sicuro che non ho mai usato a che fare con più di una dozzina di formati (e ho potuto probabilmente avere ottenuto sostenendo che solo la metà molti). OTOH, anche con tutto ciò che era ancora abbastanza veloce. Sostenere un minor numero di mercati potrebbe aver limitato il loro mercato al punto che il codice sarebbe in realtà sono stati più lenti (solo per esempio, ha gestito i file JPEG più veloce di IJG).

Come altri hanno detto, c'è un certo overhead quando si aggiunge una libreria dinamica. Quando la libreria viene caricata prima, trasferimenti devono essere eseguite, anche se questo dovrebbe essere un costo minore se la libreria viene compilata correttamente. Il costo di guardare in alto singoli simboli è anche aumentato in quanto il numero di librerie che devono essere ricercate è aumentato.

Il costo in memoria di aggiungere un altro libreria dinamica dipende in gran parte da come gran parte di essa che effettivamente utilizzate. Una pagina di codice non verrà caricato da disco fino a quando viene eseguito qualcosa su di esso. Tuttavia, saranno caricati altri dati, come le intestazioni, le tabelle di simboli e tabelle hash incorporati nel file di libreria, e questi sono in genere proporzionale alla dimensione della biblioteca.

C'è un href="http://people.redhat.com/drepper/dsohowto.pdf" rel="nofollow noreferrer"> grande documento da Ulrich Drepper, il contribuente portare a glibc, che descrive il processo e l'overhead di librerie dinamiche.

dipende da come funziona il linker. Alcuni linker sono pigri e includerà tutto il codice in libreria. I linker più efficienti potranno estrarre solo il codice necessario da una libreria. Ho avuto esperienza con entrambi i tipi.

biblioteche più piccole avranno meno preoccupazioni con entrambi i tipi di linker. Nel peggiore dei casi, con una piccola biblioteca è una piccola quantità di codice non utilizzato. Molte piccole librerie possono aumentare il tempo di costruzione. Il fuori commercio sarebbe il tempo vs spazio di codice a costruire.

Un test interessante del linker è il classico Ciao Mondo :

#include <stdio>
#include <stdlib>
int main(void)
{
  printf("Hello World\n");
  return EXIT_SUCCESS;
}

La funzione printf ha un sacco di dipendenze a causa di tutta la formattazione che possono necessità. Un linker pigro, ma veloce può comportare una "libreria standard" per risolvere tutti i simboli. Una biblioteca più efficiente includerà solo printf e le sue dipendenze. Questo rende il linker più lento.

Il programma di cui sopra può essere paragonato a questo utilizzando puts:

#include <stdio>
#include <stdlib>
int main(void)
{
  puts("Hello World\n");
  return EXIT_SUCCESS;
}

In generale, la versione puts dovrebbe essere inferiore rispetto alla versione printf, perché puts non ha alcuna formattazione ha bisogno quindi meno dipendenze. linker pigri genereranno le stesse dimensioni codice come il programma printf.

In sintesi, le decisioni di dimensione di biblioteca hanno più dipendenze il linker. Specificamente, l'efficienza del linker. In caso di dubbio, molte piccole librerie si baserà meno sull'efficienza del linker, ma rendere il processo di compilazione più complicato e più lento.

  1. La cosa a che fare con problemi di prestazioni, in generale, non è quello di farli divertire, perché per farlo è quello di essere indovinare che sono un problema, perché se non lo fai so sono, si sta indovinando, e indovinando è il concetto centrale dietro "ottimizzazione prematura". La cosa a che fare con problemi di prestazioni è, se li avete, e non prima , li diagnosticare. I problemi sono quasi mai qualcosa che avrebbe mai immaginato. Ecco un esempio esteso.

  2. Se lo fai una discreta quantità, si arriverà a riconoscere gli approcci progettuali che tendono a causare problemi di prestazioni, sia nel codice o in una libreria. (Biblioteche possono certamente avere problemi di prestazioni.) Quando si impara che e applicarlo ai progetti allora in senso si sono prematuramente ottimizzando, ma ha l'effetto desiderato in ogni caso, di evitare problemi. Se posso riassumere ciò che si avrà probabilmente imparare, è che troppi livelli di astrazione, e le gerarchie di classi esagerate (in particolare quelle piene di aggiornamento di notifica in stile) sono ciò che sono molto spesso le cause dei problemi di prestazioni.

Allo stesso tempo, condivido la tua circospezione sulle librerie terze parti e così via. Troppe volte ho lavorato su progetti in cui qualche pacchetto 3rd-party era "leva finanziaria" per "sinergia", e poi il venditore sia andato in fumo o abbandonati al prodotto o ha avuto andare obsoleto perché Microsoft ha cambiato le cose nel sistema operativo. Allora il nostro prodotto che si appoggiò pesantemente sulla confezione 3rd-party non inizia a lavorare, che richiedono una grande spesa da parte nostra, mentre i programmatori originali sono ormai lontani.

"un'altra palla al piede". Davvero?

O è una piattaforma stabile e affidabile che consente l'applicazione in primo luogo?

Si consideri che alcune persone possono, come un "troppo grande e ... gonfio" libreria perché lo usano per altri progetti e davvero fiducia in esso.

In realtà, essi possono rifiutarsi di pasticciare con il software in particolare perché hai evitato utilizzando l'ovvio "troppo grande e ... gonfio" biblioteca.

Tecnicamente, la risposta è che sì, lo fanno. Tuttavia, queste inefficienze sono molto di rado praticamente importante. Ho intenzione di assumere un linguaggio compilato staticamente come C, C ++, o D qui.

Quando un file eseguibile viene caricato in memoria su un sistema operativo moderno, lo spazio indirizzo è semplicemente mappato ad esso. Ciò significa che, non importa quanto grande sia l'exectable è, se ci sono interi blocchi pagina in formato di codice che non vengono utilizzati, non potranno mai toccare la memoria fisica. Si sprecare spazio degli indirizzi, anche se, di tanto in tanto e questo può importa un po 'nei sistemi a 32-bit.

Quando si collega a una biblioteca, un buon linker di solito buttare via roba in eccesso che non si utilizza, anche se soprattutto nel caso di istanze di template questo non sempre avviene. Così i binari potrebbero essere un po 'più grande di quanto strettamente necessario.

Se si dispone di codice che non si utilizzano pesantemente Interleaved con il codice che si utilizzati, si può finire per sprecare spazio nella cache della CPU. Tuttavia, come linee di cache sono piccoli (solitamente 64 byte), ciò accade raramente in misura praticamente importante.

Chiedetevi che cosa il vostro obiettivo è. Si tratta di una postazione di lavoro fascia medio di oggi - nessun problema. E 'hardware più vecchio o anche un sistema embedded limitato, allora potrebbe essere.

Come precedente utenti hanno detto, basta avere il codice lì non costerà molto in termini di prestazioni (che potrebbe ridurre la località per le cache e aumentare i tempi di caricamento).

FWIW, io lavoro su Microsoft Windows e quando costruiamo di Windows; build compilata per size sono più veloci di build compilata per VELOCITA ', perché si prende meno errore di pagina non colpisce.

FFTW e ATLAS sono due molto grandi biblioteche. Stranamente, giocano grandi ruoli nel software più veloce del mondo, le applicazioni ottimizzate per funzionare su supercalcolatori. No, utilizzando librerie di grandi dimensioni non rende il codice lento, soprattutto quando l'alternativa è attuazione FFT o routine di BLAS per te stesso.

Sei molto diritto di essere preoccupato, soprattutto quando si tratta di aumentare. Non è tanto per chiunque scriva loro di essere incompetenti ma a causa di due problemi.

  1. Modelli sono solo codice intrinsecamente gonfio. Questo non importa come molto più di 10 anni fa, ma oggi la CPU è molto più veloce di accesso alla memoria e questa tendenza continua. Vorrei quasi dire i modelli sono una caratteristica obsolescenza.

Non è poi così male per il codice utente che di solito è un po 'pratico, ma in molte biblioteche tutto è definito in termini di altri modelli o modelli su elementi multipli (cioè esplosioni codice del modello esponenziale).

La semplice aggiunta in iostream aggiunge circa 3 MB (!!!) al codice. Ora aggiungere in qualche spinta sciocchezze e si dispone di 30 MB di codice se si sinply dichiarare un paio di strutture di dati particolarmente strano.

Peggio ancora, non si può nemmeno facilmente Profilo. Posso dirvi la differenza tra il codice scritto da me e il codice da librerie di template è drammatica, ma per un approccio più naieve si potrebbe pensare che si sta facendo peggio da un semplice test, ma il costo in codice di gonfiare prenderò il suo strumento in una grande realworld app.

  1. Complessità. Quando si guardano le cose in Boost, sono tutte cose che complicano il codice per un enorme grado. Cose come i puntatori intelligenti, funtori, ogni genere di cose complicate. Ora, non voglio dire che non è mai una buona idea di utilizzare questa roba, ma praticamente tutto ha un grande costo di qualche tipo. Soprattutto se non si capisce esattamente, voglio dire esattamente, quello che sta facendo.

Ma le persone erano entusiaste e fingere che ha qualcosa a che fare con il 'design' così la gente ha l'impressione che sia il modo si dovrebbe fare tutto, non solo alcuni strumenti estremamente specializzati che devono essere utilizzati di rado. Se mai.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top