Setiap kali saya menyebutkan kinerja lambat C ++ iostreams perpustakaan standar, saya bertemu dengan gelombang ketidakpercayaan. Namun saya memiliki hasil profiler yang menunjukkan sejumlah besar waktu yang dihabiskan dalam kode pustaka iostream (optimisasi kompiler penuh), dan beralih dari iostreams ke I / O API khusus OS dan manajemen buffer kustom tidak memberikan urutan peningkatan yang besar.
Apa pekerjaan tambahan yang dilakukan pustaka standar C ++, apakah diperlukan oleh standar, dan apakah berguna dalam praktiknya? Atau apakah beberapa kompiler menyediakan implementasi iostreams yang kompetitif dengan manajemen buffer manual?
Tolak ukur
Untuk menyelesaikan masalah, saya telah menulis beberapa program singkat untuk melatih buffer internal iostreams:
- menempatkan data biner ke dalam
ostringstream
http://ideone.com/2PPYw - menempatkan data biner ke dalam
char[]
buffer http://ideone.com/Ni5ct - memasukkan data biner ke dalam http://ideone.com/Mj2Fi
vector<char>
menggunakanback_inserter
- BARU :
vector<char>
iterator sederhana http://ideone.com/9iitv - BARU : menempatkan data biner langsung ke
stringbuf
http://ideone.com/qc9QA - BARU :
vector<char>
iterator sederhana plus batas periksa http://ideone.com/YyrKy
Perhatikan bahwa ostringstream
dan stringbuf
versi menjalankan lebih sedikit iterasi karena mereka jauh lebih lambat.
Pada ideone, ostringstream
ini sekitar 3 kali lebih lambat dari std:copy
+ back_inserter
+ std::vector
, dan sekitar 15 kali lebih lambat daripada memcpy
menjadi buffer mentah. Ini terasa konsisten dengan profil sebelum dan sesudah ketika saya mengganti aplikasi asli saya ke buffering kustom.
Ini semua adalah buffer dalam memori, sehingga lambatnya iostreams tidak dapat disalahkan pada I / O disk yang lambat, terlalu banyak pembilasan, sinkronisasi dengan stdio, atau hal lain apa pun yang digunakan orang untuk memaafkan kelambatan yang diamati dari perpustakaan standar C ++ iostream.
Akan menyenangkan untuk melihat tolok ukur pada sistem lain dan komentar tentang hal-hal implementasi yang umum dilakukan (seperti libc ++ gcc, Visual C ++, Intel C ++) dan berapa banyak overhead yang diamanatkan oleh standar.
Dasar pemikiran untuk tes ini
Sejumlah orang telah dengan benar menunjukkan bahwa iostreams lebih umum digunakan untuk output yang diformat. Namun, mereka juga satu-satunya API modern yang disediakan oleh standar C ++ untuk akses file biner. Tetapi alasan sebenarnya untuk melakukan tes kinerja pada buffering internal berlaku untuk I / O yang diformat secara khas: jika iostreams tidak dapat menyimpan pengontrol disk yang disertakan dengan data mentah, bagaimana mereka dapat mengikuti ketika mereka juga bertanggung jawab untuk memformat?
Timing Benchmark
Semua ini adalah per iterasi dari k
loop luar ( ).
Pada ideone (gcc-4.3.4, OS dan perangkat keras yang tidak dikenal):
ostringstream
: 53 milidetikstringbuf
: 27 msvector<char>
danback_inserter
: 17,6 msvector<char>
dengan iterator biasa: 10,6 msvector<char>
iterator dan batas memeriksa: 11,4 mschar[]
: 3,7 ms
Di laptop saya (Visual C ++ 2010 x86,, cl /Ox /EHsc
Windows 7 Ultimate 64-bit, Intel Core i7, 8 GB RAM):
ostringstream
: 73,4 milidetik, 71,6 msstringbuf
: 21,7 ms, 21,3 msvector<char>
danback_inserter
: 34,6 ms, 34,4 msvector<char>
dengan iterator biasa: 1,10 ms, 1,04 msvector<char>
iterator dan batas memeriksa: 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 x86, dengan Optimization Profil-Guided cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, run, link /ltcg:pgo
, ukuran:
ostringstream
: 61,2 ms, 60,5 msvector<char>
dengan iterator biasa: 1,04 ms, 1,03 ms
Laptop yang sama, OS yang sama, menggunakan cygwin gcc 4.3.4 g++ -O3
:
ostringstream
: 62,7 ms, 60,5 msstringbuf
: 44,4 ms, 44,5 msvector<char>
danback_inserter
: 13,5 ms, 13,6 msvector<char>
dengan iterator biasa: 4,1 ms, 3,9 msvector<char>
iterator dan batas memeriksa: 4.0 ms, 4.0 mschar[]
: 3,57 ms, 3,75 ms
Laptop yang sama, Visual C ++ 2008 SP1, cl /Ox /EHsc
:
ostringstream
: 88,7 ms, 87,6 msstringbuf
: 23,3 ms, 23,4 msvector<char>
danback_inserter
: 26.1 ms, 24.5 msvector<char>
dengan iterator biasa: 3,13 ms, 2,48 msvector<char>
iterator dan batas memeriksa: 2,97 ms, 2,53 mschar[]
: 1,52 ms, 1,25 ms
Laptop yang sama, kompiler Visual C ++ 2010 64-bit:
ostringstream
: 48,6 ms, 45,0 msstringbuf
: 16.2 ms, 16.0 msvector<char>
danback_inserter
: 26,3 ms, 26,5 msvector<char>
dengan iterator biasa: 0,87 ms, 0,89 msvector<char>
iterator dan batas memeriksa: 0,99 ms, 0,99 mschar[]
: 1,25 ms, 1,24 ms
EDIT: Berlari semua dua kali untuk melihat seberapa konsisten hasilnya. IMO cukup konsisten.
CATATAN: Di laptop saya, karena saya bisa menghemat lebih banyak waktu CPU daripada yang diizinkan ideone, saya mengatur jumlah iterasi ke 1000 untuk semua metode. Ini berarti bahwa ostringstream
dan vector
realokasi, yang hanya terjadi pada pass pertama, harus memiliki dampak kecil pada hasil akhir.
EDIT: Ups, menemukan bug di- vector
dengan-iterator biasa, iterator tidak sedang maju dan karena itu ada terlalu banyak cache hit. Saya bertanya-tanya bagaimana vector<char>
kinerjanya char[]
. Itu tidak membuat banyak perbedaan, vector<char>
masih lebih cepat daripada di char[]
bawah VC ++ 2010.
Kesimpulan
Buffer aliran output membutuhkan tiga langkah setiap kali data ditambahkan:
- Periksa apakah blok yang masuk sesuai dengan ruang buffer yang tersedia.
- Salin blok masuk.
- Perbarui pointer akhir data.
Cuplikan kode terbaru yang saya posting, " vector<char>
iterator sederhana plus batas cek" tidak hanya melakukan ini, tetapi juga mengalokasikan ruang tambahan dan memindahkan data yang ada saat blok yang masuk tidak sesuai. Seperti Clifford tunjukkan, buffering di file I / O class tidak perlu melakukan itu, itu hanya akan menyiram buffer saat ini dan menggunakannya kembali. Jadi ini harus menjadi batas atas pada biaya buffering output. Dan itulah yang dibutuhkan untuk membuat buffer di dalam memori yang berfungsi.
Jadi mengapa stringbuf
2.5x lebih lambat pada ideone, dan setidaknya 10 kali lebih lambat ketika saya mengujinya? Ini tidak digunakan secara polimorfis dalam patok ukur mikro sederhana ini, jadi itu tidak menjelaskannya.
std::ostringstream
tidak cukup pintar untuk secara eksponensial meningkatkan ukuran std::vector
buffernya, itu (A) bodoh dan (B) sesuatu yang dipikirkan orang tentang kinerja I / O. Bagaimanapun, buffer akan digunakan kembali, itu tidak dialokasikan kembali setiap waktu. Dan std::vector
juga menggunakan buffer yang tumbuh secara dinamis. Saya mencoba bersikap adil di sini.
ostringstream
dan Anda ingin kinerja secepat mungkin maka Anda harus mempertimbangkan untuk langsung melakukannya stringbuf
. The ostream
kelas kira untuk mengikat fungsi format bersama-sama lokal menyadari dengan pilihan fleksibel penyangga (file, string, dll) melalui rdbuf()
dan antarmuka fungsi virtual. Jika Anda tidak melakukan pemformatan apa pun, tingkat tipuan ekstra itu tentu akan terlihat mahal secara proporsional dibandingkan dengan pendekatan lain.
ofstream
ke fprintf
ketika keluaran Info logging yang melibatkan ganda. MSVC 2008 di WinXPsp3. iostreams hanya lambat anjing.