Lo STL dovrebbe essere evitato in applicazioni di grandi dimensioni?


24

Potrebbe sembrare una domanda strana, ma nel mio dipartimento abbiamo problemi con la seguente situazione:

Stiamo lavorando su un'applicazione server, che sta diventando sempre più grande, anche nel momento in cui stiamo considerando di dividerlo in parti diverse (file DLL), caricando dinamicamente quando necessario e scaricandolo in seguito, al fine di essere in grado di gestire i problemi di prestazione.

Ma: le funzioni che stiamo usando, stanno passando parametri di input e output come oggetti STL, e come menzionato in una risposta Stack Overflow , questa è una pessima idea. (Il post contiene alcune soluzioni ± e hack, ma non sembra molto solido.)

Ovviamente potremmo sostituire i parametri di input / output con tipi C ++ standard e creare oggetti STL da quelli che si trovavano all'interno delle funzioni, ma ciò potrebbe causare un calo delle prestazioni.

Va bene concludere che, nel caso in cui si stia considerando di creare un'applicazione, che potrebbe diventare così grande che un singolo PC non è più in grado di gestirla, non è necessario utilizzare STL come tecnologia?

Ulteriori informazioni su questa domanda:
sembrano esserci dei malintesi sulla domanda: il problema è il seguente: La
mia applicazione sta usando enormi quantità di prestazioni (CPU, memoria) per completare il suo lavoro e vorrei dividere questo lavoro in diverse parti (poiché il programma è già suddiviso in più funzioni), non è così difficile creare alcune DLL dalla mia applicazione e inserire alcune delle funzioni nella tabella di esportazione di tali DLL. Ciò comporterebbe la seguente situazione:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 è l'istanza dell'applicazione, installata su Machine1, mentre App_Inst2 è l'istanza della stessa applicazione, installata su Machine2.
DLL1.x è una DLL, installata su Machine1, mentre DLL2.x è una DLL, installata su Machine2.
DLLx.1 copre la funzione esportata 1.
DLLx.2 copre la funzione esportata2.

Ora su Machine1 mi piacerebbe eseguire function1 e function2. So che questo sovraccaricherà Machine1, quindi vorrei inviare un messaggio ad App_Inst2, chiedendo a quell'istanza dell'applicazione di eseguire function2.

I parametri di input / output di function1 e function2 sono oggetti STL (C ++ Standard Type Library) e regolarmente potrei aspettarmi che il cliente esegua aggiornamenti di App_Inst1, App_Inst2, DLLx.y (ma non tutti, il cliente potrebbe aggiornare Machine1 ma non Machine2, o solo aggiornare le applicazioni ma non le DLL o viceversa, ...). Ovviamente se l'interfaccia (parametri di input / output) cambia, il cliente è costretto a fare aggiornamenti completi.

Tuttavia, come menzionato nell'URL StackOverflow indicato, una semplice ricompilazione di App_Inst1 o di una delle DLL potrebbe causare la caduta dell'intero sistema, quindi il mio titolo originale di questo post, sconsigliare l'utilizzo di STL (modello standard C ++ Libreria) per grandi applicazioni.

Spero di aver chiarito alcune domande / dubbi.


44
Sei sicuro di avere problemi di prestazioni a causa delle dimensioni del tuo eseguibile ? Puoi aggiungere alcuni dettagli sul fatto che sia realistico supporre che tutto il tuo software sia compilato con lo stesso compilatore (ad esempio in una volta sul server di build) o se desideri effettivamente suddividere in team indipendenti?
nvoigt,

5
Fondamentalmente hai bisogno di una persona il cui lavoro dedicato sia "build manager" e "release manager", per garantire che tutti i progetti C ++ vengano compilati sulla stessa versione del compilatore e con identiche impostazioni del compilatore C ++, compilate da un'istantanea coerente (versione) del sorgente codice, ecc. In genere questo viene gestito sotto la bandiera di "integrazione continua". Se cerchi online troverai molti articoli e strumenti. Le pratiche obsolete possono auto-rafforzarsi: una pratica obsoleta può portare a tutte le pratiche obsolete.
rwong

8
La risposta accettata nella domanda collegata indica che il problema riguarda le chiamate C ++ in generale. Quindi "C ++ ma non STL" non aiuta, devi andare con C nudo per essere al sicuro (ma vedi anche le risposte, la serializzazione è probabilmente una soluzione migliore).
Frax,

52
caricare dinamicamente quando necessario e scaricarlo in seguito, al fine di essere in grado di gestire i problemi di prestazioni Quali "problemi di prestazioni"? Non conosco problemi diversi dall'utilizzo di troppa memoria che può essere risolta scaricando cose come DLL dalla memoria - e se questo è il problema, la soluzione più semplice è semplicemente acquistare più RAM. Hai profilato la tua applicazione per identificare i colli di bottiglia delle prestazioni effettive? Perché questo sembra un problema XY - hai "problemi di prestazioni" non specificati e qualcuno ha già deciso la soluzione.
Andrew Henle,

4
@MaxBarraclough "The STL" è perfettamente accettato come nome alternativo per i contenitori e le funzioni basati su modelli che sono stati inseriti nella libreria standard C ++. In effetti le Linee guida di base C ++, scritte da Bjarne Stroustrup e Herb Sutter, fanno ripetutamente riferimento a "la STL" quando ne parlano. Non puoi ottenere una fonte molto più autorevole di quella.
Sean Burton,

Risposte:


110

Questo è un classico problema XY freddo come la pietra.

Il tuo vero problema sono i problemi di prestazioni. Tuttavia, la tua domanda chiarisce che non hai eseguito la profilazione o altre valutazioni sulla provenienza dei problemi di prestazioni. Invece speri che la suddivisione del codice in DLL risolva magicamente il problema (cosa che non lo farà, per la cronaca), e ora sei preoccupato per un aspetto di quella non soluzione.

Invece, devi risolvere il vero problema. Se hai più eseguibili, controlla quale causa il rallentamento. Mentre ci sei, assicurati che il programma richieda tutto il tempo di elaborazione e non un driver Ethernet mal configurato o qualcosa del genere. E dopo, inizia a profilare le varie attività nel tuo codice. Il timer ad alta precisione è il tuo amico qui. La soluzione classica è monitorare i tempi di elaborazione medi e nel caso peggiore per un pezzo di codice.

Quando hai i dati, puoi capire come affrontare il problema e quindi capire dove ottimizzare.


54
"Invece speri che la suddivisione del codice in DLL risolva magicamente il problema (cosa che non è, per la cronaca)" - +1 per questo. Il tuo sistema operativo implementa quasi sicuramente il paging della domanda che ottiene esattamente lo stesso risultato del caricamento e dello scaricamento delle funzionalità nelle DLL, solo automaticamente piuttosto che richiedere un intervento manuale. Anche se sei in grado di prevedere quanto tempo dovrebbe rimanere in sospeso un pezzo di codice una volta utilizzato rispetto al sistema di memoria virtuale del sistema operativo (che è in realtà improbabile), il sistema operativo memorizzerà nella cache il file DLL e annullerà comunque i tuoi sforzi .
Jules,

@Jules Vedi aggiornamento - hanno chiarito che le DLL esistono solo su macchine separate, quindi posso vedere questa soluzione funzionare. Ora c'è un sovraccarico di comunicazione, quindi è difficile esserne certi.
Izkata,

2
@Izkata - non è ancora del tutto chiaro, ma penso che ciò che è descritto sia che vogliono selezionare dinamicamente (in base alla configurazione di runtime) una versione di ogni funzione che sia locale o remota. Ma qualsiasi parte del file EXE che non viene mai utilizzata su una determinata macchina semplicemente non verrà mai caricata in memoria, quindi non è necessario l'uso di DLL per questo scopo. Includi entrambe le versioni di tutte le funzioni nella build standard e crea una tabella di puntatori di funzione (o oggetti richiamabili C ++ o qualsiasi metodo tu preferisca) per invocare la versione appropriata di ciascuna funzione.
Jules,

38

Se devi dividere un software tra più macchine fisiche, devi avere una qualche forma di serializzazione quando passi i dati tra macchine poiché solo in alcuni casi puoi semplicemente inviare lo stesso binario esatto tra macchine. La maggior parte dei metodi di serializzazione non ha problemi a gestire i tipi di STL, quindi quel caso non è qualcosa che mi preoccuperebbe.

Se devi dividere un'applicazione in Librerie condivise (DLL) (prima di farlo per motivi di prestazioni, dovresti davvero assicurarti che risolva effettivamente i tuoi problemi di prestazioni) passare oggetti STL può essere un problema ma non deve esserlo. Come già descritto nel collegamento fornito, il passaggio di oggetti STL funziona se si utilizzano lo stesso compilatore e le stesse impostazioni del compilatore. Se gli utenti forniscono le DLL, potresti non essere in grado di contare facilmente su questo. Se fornisci tutte le DLL e compili tutto insieme, tuttavia, potresti essere in grado di contare su di esso e l'utilizzo di oggetti STL oltre i confini della DLL diventa molto possibile. Devi ancora fare attenzione alle impostazioni del compilatore in modo da non ottenere più cumuli diversi se passi la proprietà dell'oggetto, anche se questo non è un problema specifico STL.


1
Sì, e in particolare la parte relativa al passaggio di oggetti allocati attraverso i limiti DLL / so. In generale, l'unico modo per evitare assolutamente il problema dell'allocatore multiplo è assicurarsi che anche la DLL (o la libreria!) Che ha allocato la struttura lo liberi. Ecco perché vedrai un sacco di API in stile C scritte in questo modo: un'API esplicita gratuita per ogni API che restituisce un array / struttura allocata. Il problema aggiuntivo con STL è che il chiamante potrebbe aspettarsi di essere in grado di modificare la complessa struttura dati passata (aggiungere / rimuovere elementi) e che anche questo non può essere consentito. Ma è difficile da applicare.
davidbak,

1
Se dovessi dividere un'applicazione come questa, probabilmente userei COM, ma questo generalmente aumenta la dimensione del codice poiché ogni componente porta le proprie librerie C e C ++ (che possono essere condivise quando sono uguali, ma possono divergere quando necessario, ad esempio durante le transizioni. Non sono convinto che questo sia il modo di agire appropriato per il problema del PO
Simon Richter,

2
Come esempio specifico, il programma è altamente probabile un posto di voler inviare un testo a un altro computer. Ad un certo punto, ci sarà un puntatore ad alcuni personaggi coinvolti nella rappresentazione di quel testo. Non puoi assolutamente trasmettere i bit di quei puntatori e aspettarti un comportamento definito sul lato ricevente
Caleth,

20

Stiamo lavorando su un'applicazione server, che sta diventando sempre più grande, anche nel momento in cui stiamo valutando di dividerlo in parti diverse (DLL), caricando dinamicamente quando necessario e scaricandolo in seguito, al fine di essere in grado di gestire il problemi di prestazione

La RAM è economica e quindi il codice inattivo è economico. Il caricamento e lo scaricamento del codice (in particolare lo scaricamento) è un processo fragile ed è improbabile che abbia un impatto significativo sulle prestazioni dei programmi sul moderno hardware desktop / server.

La cache è più costosa ma influisce solo sul codice recentemente attivo, non sul codice che si trova in memoria inutilizzato.

In generale, i programmi superano i loro computer a causa delle dimensioni dei dati o del tempo della CPU, non delle dimensioni del codice. Se la dimensione del tuo codice sta diventando così grande da causare grossi problemi, probabilmente vorrai esaminare perché ciò sta accadendo in primo luogo.

Ma: le funzioni che stiamo usando, stanno passando parametri di input e output come oggetti STL, e come menzionato in questo URL StackOverflow, questa è una pessima idea.

Dovrebbe essere ok fintanto che le DLL e l'eseguibile sono tutte costruite con lo stesso compilatore e collegate dinamicamente alla stessa libreria di runtime C ++. Ne consegue che se l'applicazione e le sue DLL associate vengono create e distribuite come una singola unità, non dovrebbe essere un problema.

Il punto in cui può diventare un problema è quando le librerie sono costruite da persone diverse o possono essere aggiornate separatamente.

Va bene concludere che, nel caso in cui si stia considerando di creare un'applicazione, che potrebbe diventare così grande che un singolo PC non può più gestirla, non è necessario utilizzare STL come tecnologia?

Non proprio.

Una volta che inizi a diffondere un'applicazione su più macchine, hai un sacco di considerazioni su come passare i dati tra quelle macchine. È probabile che i dettagli sull'utilizzo dei tipi STL o di altri tipi di base vengano persi nel rumore.


2
Il codice inattivo probabilmente non viene mai caricato nella RAM in primo luogo. La maggior parte dei sistemi operativi carica pagine da file eseguibili solo se effettivamente richiesti.
Jules,

1
@Jules: se il codice morto viene mischiato con il codice live (con dimensioni della pagina = granularità 4k), verrà mappato + caricato. La cache funziona con una granularità molto più fine (64 B), quindi è per lo più vero che le funzioni inutilizzate non fanno molto male. Ogni pagina ha bisogno di una voce TLB, tuttavia (e diversamente dalla RAM) che è una risorsa di runtime scarsa. (Le mappature supportate da file in genere non usano gli hugepage, almeno non su Linux; un hugepage è 2MiB su x86-64, quindi puoi coprire molto più codice o dati senza ottenere alcun errore TLB con gli hugepage.)
Peter Cordes

1
Cosa rileva @PeterCordes: Quindi, assicurati di usare "PGO" come parte del tuo processo di build-for-release!
JDługosz,

13

No, non credo che la conclusione segua. Anche se il tuo programma è distribuito su più macchine, non c'è motivo per cui l'uso dell'STL ti costringa a usarlo nella comunicazione tra moduli / processi.

In effetti, direi che dovresti separare dall'inizio la progettazione di interfacce esterne dall'implementazione interna, poiché la prima sarà più solida / difficile da cambiare rispetto a quella utilizzata internamente


7

Ti manca il punto di quella domanda.

Esistono fondamentalmente due tipi di DLL. Il tuo e quello di qualcun altro. Il "problema STL" è che tu e loro potreste non usare lo stesso compilatore. Ovviamente, questo non è un problema per la tua DLL.


5

Se si creano le DLL dallo stesso albero di origine contemporaneamente con lo stesso compilatore e opzioni di compilazione, funzionerà correttamente.

Tuttavia, il modo "Windows" di dividere un'applicazione in più parti, alcune delle quali sono riutilizzabili, sono i componenti COM . Questi possono essere piccoli (controlli singoli o codec) o grandi (IE è disponibile come controllo COM, in mshtml.dll).

caricare dinamicamente quando necessario e scaricare successivamente

Per un'applicazione server, questo avrà probabilmente una terribile efficienza; è realmente praticabile solo quando si dispone di un'applicazione che attraversa più fasi per un lungo periodo di tempo, in modo da sapere quando qualcosa non sarà più necessario. Mi ricorda i giochi DOS che usano il meccanismo di sovrapposizione.

Inoltre, se il tuo sistema di memoria virtuale funziona correttamente, lo gestirà eseguendo il paging di pagine di codice inutilizzate.

potrebbe crescere così tanto che un singolo PC non può più gestirlo

Acquista un PC più grande.

Non dimenticare che con la giusta ottimizzazione un laptop può superare un cluster hadoop.

Se hai davvero bisogno di più sistemi, devi pensare molto attentamente al confine tra loro, poiché è qui che si trova il costo di serializzazione. È qui che dovresti iniziare a guardare framework come MPI.


1
"è davvero praticabile solo quando si dispone di un'applicazione che si muove attraverso più fasi per un lungo periodo di tempo in modo da sapere quando qualcosa non sarà più necessario" - anche allora è improbabile che aiuti molto, perché il sistema operativo lo farà memorizza nella cache i file DLL, che probabilmente finiranno per occupare più memoria rispetto al semplice includere le funzioni direttamente nell'eseguibile di base. Le sovrapposizioni sono utili solo nei sistemi senza memoria virtuale o quando lo spazio degli indirizzi virtuali è il fattore limitante (presumo che questa applicazione sia a 64 bit, non a 32 ...).
Jules,

3
"Acquista un PC più grande" +1. Ora puoi acquisire sistemi con più terabyte di RAM. Puoi assumerne uno da Amazon a un prezzo inferiore alla tariffa oraria di un singolo sviluppatore. Quanto tempo impiegherai lo sviluppatore a ottimizzare il codice per ridurre l'utilizzo della memoria?
Jules,

2
Il problema più grande che ho riscontrato con "acquista un PC più grande" era legato alla domanda "fino a che punto ridimensionerà la tua app?". La mia risposta è stata "quanto sei disposto a spendere per un test? Perché mi aspetto che si riduca così tanto che il noleggio di una macchina adeguata e l'installazione di un test adeguatamente ampio costerà migliaia di dollari. Nessuno dei nostri clienti è nemmeno vicino a ciò che può fare un PC a CPU singola ". Molti programmatori più anziani non hanno idea realistica di quanto siano cresciuti i PC; la videocard da sola nei moderni PC è un supercomputer secondo gli standard del 20 ° secolo.
MSalters

Componenti COM? Forse negli anni '90, ma ora?
Peter Mortensen,

@MSalters - giusto ... chiunque abbia domande su quanto può essere scalabile un'applicazione su un singolo PC dovrebbe guardare le specifiche per il tipo di istanza Amazon EC2 x1e.32xlarge - 72 core di processori fisici totali nella macchina fornendo 128 core virtuali a 2,3 GHz (espandibile a 3,1 GHz), potenzialmente fino a 340 GB / s di larghezza di banda di memoria (a seconda del tipo di memoria installata, non descritta nelle specifiche) e 3,9 TB di RAM. Ha abbastanza cache per eseguire la maggior parte delle applicazioni senza mai toccare la RAM principale. Anche senza GPU è potente come un cluster di supercomputer a 500 nodi del 2000.
Jules

0

Stiamo lavorando su un'applicazione server, che sta diventando sempre più grande, anche nel momento in cui stiamo considerando di dividerlo in parti diverse (file DLL), caricando dinamicamente quando necessario e scaricandolo in seguito, al fine di essere in grado di gestire i problemi di prestazione.

La prima parte ha senso (suddividere l'applicazione su macchine diverse, per motivi di prestazioni).

La seconda parte (caricamento e scaricamento delle librerie) non ha senso, in quanto si tratta di uno sforzo supplementare da compiere e non migliorerà (davvero) le cose.

Il problema che stai descrivendo è meglio risolto con macchine di calcolo dedicate, ma queste non dovrebbero funzionare con la stessa (principale) applicazione.

La soluzione classica si presenta così:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Tra le macchine front-end e di calcolo, potresti avere cose extra, come i bilanciatori del carico e il monitoraggio delle prestazioni, e il keeing dell'elaborazione specializzata su macchine dedicate è buono per l'ottimizzazione della cache e del throughput.

Ciò non implica in alcun modo un ulteriore carico / scarico di DLL, né nulla a che fare con l'STL.

Cioè, usa STL internamente come richiesto e serializza i tuoi dati tra gli elementi (vedi grpc e buffer di protocollo e il tipo di problemi che risolvono).

Detto questo, con le informazioni limitate fornite, questo sembra il classico problema xy (come ha detto @Graham).

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.