Ogni volta che menziono le lente prestazioni degli iostreams della libreria standard C ++, mi viene incontro un'ondata di incredulità. Tuttavia ho risultati del profiler che mostrano grandi quantità di tempo speso nel codice della libreria iostream (ottimizzazioni complete del compilatore) e il passaggio da iostreams ad API I / O specifiche del sistema operativo e la gestione del buffer personalizzata offre un ordine di miglioramento della grandezza.
Quale lavoro aggiuntivo sta facendo la libreria standard C ++, è richiesta dallo standard ed è utile nella pratica? Oppure alcuni compilatori forniscono implementazioni di iostreams competitivi con la gestione manuale del buffer?
Punti di riferimenti
Per far muovere le cose, ho scritto un paio di brevi programmi per esercitare il buffering interno degli iostreams:
- inserendo dati binari in un
ostringstream
http://ideone.com/2PPYw - mettere i dati binari in un
char[]
buffer http://ideone.com/Ni5ct - inserendo dati binari in un http://ideone.com/Mj2Fi
vector<char>
usandoback_inserter
- NOVITÀ :
vector<char>
iteratore semplice http://ideone.com/9iitv - NOVITÀ : inserimento di dati binari direttamente su
stringbuf
http://ideone.com/qc9QA - NOVITÀ :
vector<char>
iteratore semplice più limiti di controllo http://ideone.com/YyrKy
Si noti che le versioni ostringstream
e stringbuf
eseguono meno iterazioni perché sono molto più lente.
Su ideone, ostringstream
è circa 3 volte più lento di std:copy
+ back_inserter
+ std::vector
e circa 15 volte più lento dimemcpy
a un buffer non elaborato. Ciò è coerente con la profilazione prima e dopo quando ho passato la mia vera applicazione al buffering personalizzato.
Questi sono tutti buffer in memoria, quindi la lentezza degli iostreams non può essere biasimata su I / O su disco lento, troppo flushing, sincronizzazione con stdio o qualsiasi altra cosa che le persone usano per scusare la lentezza osservata della libreria standard C ++ iostream.
Sarebbe bello vedere benchmark su altri sistemi e commenti su cose che fanno le comuni implementazioni (come libc ++ di gcc, Visual C ++, Intel C ++) e su quanto dell'overhead è richiesto dallo standard.
Razionale per questo test
Alcune persone hanno correttamente sottolineato che gli iostreams sono più comunemente usati per l'output formattato. Tuttavia, sono anche l'unica API moderna fornita dallo standard C ++ per l'accesso ai file binari. Ma il vero motivo per eseguire test delle prestazioni sul buffering interno si applica al tipico I / O formattato: se gli iostreams non riescono a mantenere il controller del disco fornito con dati non elaborati, come possono tenere il passo anche quando sono responsabili della formattazione?
Tempistica di riferimento
Tutti questi sono per iterazione dell'esterno (k
ciclo ).
Su ideone (gcc-4.3.4, SO e hardware sconosciuti):
ostringstream
: 53 millisecondistringbuf
: 27 msvector<char>
eback_inserter
: 17,6 msvector<char>
con iteratore ordinario: 10,6 msvector<char>
controllo iteratore e limiti: 11,4 mschar[]
: 3,7 ms
Sul mio laptop (Visual C ++ 2010 x86, cl /Ox /EHsc
Windows 7 Ultimate a 64 bit, Intel Core i7, 8 GB RAM):
ostringstream
: 73,4 millisecondi, 71,6 msstringbuf
: 21,7 ms, 21,3 msvector<char>
eback_inserter
: 34,6 ms, 34,4 msvector<char>
con iteratore ordinario: 1,10 ms, 1,04 msvector<char>
controllo iteratore e limiti: 1,11 ms, 0,87 ms, 1,12 ms, 0,89 ms, 1,02 ms, 1,14 mschar[]
: 1,48 ms, 1,57 ms
Visual C ++ 2010 x 86, con profilo-Guided Optimization cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, corsa, link /ltcg:pgo
, misura:
ostringstream
: 61,2 ms, 60,5 msvector<char>
con iteratore ordinario: 1,04 ms, 1,03 ms
Stesso laptop, stesso sistema operativo, utilizzando cygwin gcc 4.3.4 g++ -O3
:
ostringstream
: 62,7 ms, 60,5 msstringbuf
: 44,4 ms, 44,5 msvector<char>
eback_inserter
: 13,5 ms, 13,6 msvector<char>
con iteratore ordinario: 4,1 ms, 3,9 msvector<char>
controllo iteratore e limiti: 4.0 ms, 4.0 mschar[]
: 3,57 ms, 3,75 ms
Stesso computer portatile, Visual C ++ 2008 SP1, cl /Ox /EHsc
:
ostringstream
: 88,7 ms, 87,6 msstringbuf
: 23,3 ms, 23,4 msvector<char>
eback_inserter
: 26,1 ms, 24,5 msvector<char>
con iteratore ordinario: 3,13 ms, 2,48 msvector<char>
controllo iteratore e limiti: 2,97 ms, 2,53 mschar[]
: 1,52 ms, 1,25 ms
Stesso laptop, compilatore Visual C ++ 2010 a 64 bit:
ostringstream
: 48,6 ms, 45,0 msstringbuf
: 16,2 ms, 16,0 msvector<char>
eback_inserter
: 26,3 ms, 26,5 msvector<char>
con iteratore ordinario: 0,87 ms, 0,89 msvector<char>
controllo iteratore e limiti: 0,99 ms, 0,99 mschar[]
: 1,25 ms, 1,24 ms
EDIT: ho eseguito tutto due volte per vedere quanto erano coerenti i risultati. IMO abbastanza coerente.
NOTA: sul mio laptop, poiché posso risparmiare più tempo della CPU di quanto ideone consenta, ho impostato il numero di iterazioni su 1000 per tutti i metodi. Ciò significa che ostringstream
e la vector
riallocazione, che ha luogo solo al primo passaggio, dovrebbe avere un impatto limitato sui risultati finali.
EDIT: Oops, vector
ho trovato un bug nell'iteratore -con-ordinario, l'iteratore non era avanzato e quindi c'erano troppi hit della cache. Mi chiedevo come vector<char>
fosse meglio char[]
. Tuttavia non ha fatto molta differenza, vector<char>
è ancora più veloce dichar[]
VC ++ 2010.
conclusioni
Il buffering dei flussi di output richiede tre passaggi ogni volta che i dati vengono aggiunti:
- Verificare che il blocco in entrata si adatti allo spazio buffer disponibile.
- Copia il blocco in arrivo.
- Aggiorna il puntatore di fine dati.
L'ultimo frammento di codice che ho pubblicato, " vector<char>
semplice iteratore più controllo dei limiti" non solo fa questo, ma alloca spazio aggiuntivo e sposta i dati esistenti quando il blocco in arrivo non si adatta. Come ha sottolineato Clifford, il buffering in una classe I / O di file non dovrebbe farlo, semplicemente svuoterebbe il buffer corrente e lo riutilizzerebbe. Quindi questo dovrebbe essere un limite superiore al costo dell'output di buffering. Ed è esattamente ciò che è necessario per creare un buffer in memoria funzionante.
Allora perché stringbuf
2,5 volte più lento su ideone e almeno 10 volte più lento quando lo collaudo? Non viene utilizzato polimorficamente in questo semplice micro-benchmark, quindi non lo spiega.
std::ostringstream
non è abbastanza intelligente da aumentare esponenzialmente le dimensioni del buffer come std::vector
fa, questo è (A) stupido e (B) qualcosa a cui le persone che pensano alle prestazioni I / O dovrebbero pensare. Ad ogni modo, il buffer viene riutilizzato, non viene riallocato ogni volta. E std::vector
sta anche usando un buffer in crescita dinamica. Sto cercando di essere onesto qui.
ostringstream
e desideri prestazioni il più veloci possibile, allora dovresti considerare di andare direttamente a stringbuf
. Si ostream
suppone che le classi colleghino la funzionalità di formattazione consapevole delle impostazioni locali con una scelta flessibile del buffer (file, stringa, ecc.) rdbuf()
E la sua interfaccia di funzione virtuale. Se non stai eseguendo alcuna formattazione, quel livello extra di indiretta sembrerà sicuramente proporzionalmente costoso rispetto ad altri approcci.
ofstream
a fprintf
quando si trasmettono informazioni di registrazione che coinvolgono i doppi. MSVC 2008 su WinXPsp3. iostreams è solo un cane lento.