Кожен раз, коли я згадую про повільну продуктивність стандартних іонів бібліотеки C ++, я зустрічаюся з хвилею невіри. Тим не менш, у мене є результати профілерів, які показують велику кількість часу, проведеного в коді бібліотеки iostream (повна оптимізація компілятора), і перехід від iostreams до API-інтерфейсів для вводу-виводу, призначених для ОС, та управління спеціальними буферами, наказує покращення масштабу.
Яку додаткову роботу виконує стандартна бібліотека C ++, чи вимагає її стандарт і чи корисна вона на практиці? Або деякі компілятори забезпечують реалізацію iostreams, які є конкурентоспроможними ручним управлінням буферами?
Орієнтири
Щоб змінити справи, я написав пару коротких програм для здійснення внутрішнього буферування iostreams:
- розміщення двійкових даних у
ostringstream
http://ideone.com/2PPYw - поміщення бінарних даних у
char[]
буфер http://ideone.com/Ni5ct - введення двійкових даних в
vector<char>
використанніback_inserter
http://ideone.com/Mj2Fi - НОВЕ :
vector<char>
простий ітератор http://ideone.com/9iitv - НОВО : розміщення двійкових даних безпосередньо в
stringbuf
http://ideone.com/qc9QA - НОВО :
vector<char>
перевірити простий ітератор плюс межі http://ideone.com/YyrKy
Зауважте, що ostringstream
і stringbuf
версії виконують менше ітерацій, оскільки вони набагато повільніші.
У ideone, ostringstream
це приблизно в 3 рази повільніше std:copy
+ back_inserter
+ std::vector
і приблизно в 15 разів повільніше, ніж memcpy
у сирому буфері. Це відповідає результатам профілювання до і після, коли я переключив реальну програму на спеціальну буферизацію.
Все це буфери в пам'яті, тому повільність іостримів не можна звинувачувати в вводному / вхідному режимі повільного диска, занадто багато промивання, синхронізації з stdio або будь-якого іншого, що люди використовують для виправдання спостережуваної повільності стандартної бібліотеки C ++ іострім.
Було б непогано побачити орієнтири інших систем та коментувати речі, які зазвичай реалізують (наприклад, gcc's libc ++, Visual C ++, Intel C ++) і скільки накладних витрат визначається стандартом.
Обґрунтування цього тесту
Ряд людей правильно вказав, що іотоми частіше використовують для форматованого виводу. Однак вони також є єдиним сучасним API, що надається стандартом C ++ для доступу до бінарних файлів. Але справжня причина для тестування продуктивності внутрішнього буферизації стосується типового форматованого вводу / виводу: якщо iostreams не можуть тримати дисковий контролер, що постачається із необробленими даними, то як вони можуть бути в курсі, коли вони також відповідають за форматування?
Орієнтовні терміни
Все це здійснюється за ітерацією зовнішньої ( k
) петлі.
Що стосується ideone (gcc-4.3.4, невідома ОС та апаратне забезпечення):
ostringstream
: 53 мілісекундиstringbuf
: 27 мсvector<char>
іback_inserter
: 17,6 мсvector<char>
зі звичайним ітератором: 10,6 мсvector<char>
Перевірка ітератора та меж: 11,4 мсchar[]
: 3,7 мс
На моєму ноутбуці (Visual C ++ 2010 x86,, cl /Ox /EHsc
Windows 7 Ultimate 64-розрядний, Intel Core i7, 8 ГБ оперативної пам’яті):
ostringstream
: 73,4 мілісекунди, 71,6 мсstringbuf
: 21,7 мс, 21,3 мсvector<char>
іback_inserter
: 34,6 мс, 34,4 мсvector<char>
зі звичайним ітератором: 1,10 мс, 1,04 мсvector<char>
Перевірка ітератора та меж: 1,11 мс, 0,87 мс, 1,12 мс, 0,89 мс, 1,02 мс, 1,14 мсchar[]
: 1,48 мс, 1,57 мс
Visual C ++ 2010 x86, з профілем Guided Optimization cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, біг, link /ltcg:pgo
, заходи:
ostringstream
: 61,2 мс, 60,5 мсvector<char>
зі звичайним ітератором: 1,04 мс, 1,03 мс
Той самий ноутбук, та сама ОС, що використовує cygwin gcc 4.3.4 g++ -O3
:
ostringstream
: 62,7 мс, 60,5 мсstringbuf
: 44,4 мс, 44,5 мсvector<char>
іback_inserter
: 13,5 мс, 13,6 мсvector<char>
зі звичайним ітератором: 4,1 мс, 3,9 мсvector<char>
Перевірка ітератора та меж: 4,0 мс, 4,0 мсchar[]
: 3,57 мс, 3,75 мс
Той же ноутбук, Visual C ++ 2008 SP1, cl /Ox /EHsc
:
ostringstream
: 88,7 мс, 87,6 мсstringbuf
: 23,3 мс, 23,4 мсvector<char>
іback_inserter
: 26,1 мс, 24,5 мсvector<char>
зі звичайним ітератором: 3,13 мс, 2,48 мсvector<char>
Перевірка ітератора та меж: 2,97 мс, 2,53 мсchar[]
: 1,52 мс, 1,25 мс
Той самий ноутбук, 64-розрядний компілятор Visual C ++ 2010:
ostringstream
: 48,6 мс, 45,0 мсstringbuf
: 16,2 мс, 16,0 мсvector<char>
іback_inserter
: 26,3 мс, 26,5 мсvector<char>
зі звичайним ітератором: 0,87 мс, 0,89 мсvector<char>
Перевірка ітератора та меж: 0,99 мс, 0,99 мсchar[]
: 1,25 мс, 1,24 мс
РЕДАКТУВАННЯ: Пробігайте всі двічі, щоб побачити, наскільки результати були. Досить послідовний ІМО.
ПРИМІТКА. Оскільки я можу заощадити більше процесорного часу, ніж дозволяє ideone, я встановив кількість ітерацій на 1000 для всіх методів. Це означає, що ostringstream
і vector
перерозподіл, який відбувається лише з першого проходу, повинен мало впливати на кінцеві результати.
EDIT: На жаль, знайдено помилку в vector
-з звичайним ітератором, ітератор не просунувся, і тому було занадто багато звернень кешу. Мені було цікаво, як vector<char>
це перевершує char[]
. Це не мало великої різниці, але vector<char>
все ж швидше, ніж char[]
у VC ++ 2010.
Висновки
Буферування вихідних потоків вимагає трьох кроків кожного разу, коли дані додаються:
- Переконайтеся, що вхідний блок відповідає доступному буферному простору.
- Скопіюйте вхідний блок.
- Оновіть покажчик кінця даних.
Останній фрагмент коду, який я опублікував, " vector<char>
простий ітератор плюс перевірка меж", це не тільки робить це, він також виділяє додатковий простір і переміщує наявні дані, коли вхідний блок не підходить. Як зазначав Кліффорд, буферизація у файлі класу вводу-виводу не повинна цього робити, вона просто промиває поточний буфер і повторно використовує його. Отже, це має бути верхньою межею витрат на буферизацію продукції. І саме те, що потрібно для створення робочого буфера пам'яті.
Так чому stringbuf
2,5 рази повільніше на ideone, і принаймні в 10 разів повільніше, коли я тестую його? Він не використовується поліморфно в цьому простому мікро-орієнтирі, так що це не пояснює.
std::ostringstream
недостатньо розумний, щоб експоненціально збільшити розмір буфера так, як std::vector
це робиться, це (А) дурне і (В) те, про що люди повинні думати про продукти вводу / виводу. У будь-якому разі буфер повторно використовується, він не перерозподіляється кожного разу. А std::vector
також використовує буфер, що динамічно зростає. Я намагаюся бути тут справедливим.
ostringstream
і ви хочете якомога швидшу продуктивність, то вам слід подумати про те, щоб перейти безпосередньо до цього stringbuf
. Заняття ostream
класів передбачають поєднання функціональних можливостей форматування з урахуванням локальних даних із гнучким вибором буфера (файл, рядок тощо) за допомогою rdbuf()
та його віртуального інтерфейсу функцій. Якщо ви не робите жодного форматування, то цей додатковий рівень непрямості, безумовно, буде пропорційно дорогим порівняно з іншими підходами.
ofstream
до, fprintf
коли виводить інформацію про реєстрацію, що включає подвійні. MSVC 2008 на WinXPsp3. iostreams просто собачий повільний.