Std:: fstream буферизация против ручного буферизации (почему 10-кратное усиление с ручной буферизацией)?
Я тестировал две конфигурации записи:
1) Буферизация Fstream:
// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream;
stream.rdbuf()->pubsetbuf(buffer, length);
stream.open("test.dat", std::ios::binary | std::ios::trunc)
// To write I use :
stream.write(reinterpret_cast<char*>(&x), sizeof(x));
2) Ручная буферизация:
// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc);
// Then I put manually the data in the buffer
// To write I use :
stream.write(buffer, length);
Я ожидал того же результата...
Но моя ручная буферизация повышает производительность в 10 раз, чтобы записать файл размером 100 МБ, а буферизация fstream ничего не меняет по сравнению с нормальной ситуацией (без переопределения буфера).
Есть ли у кого-нибудь объяснение этой ситуации?
ИЗМЕНИТЬ:
Вот новость: тест, сделанный только на суперкомпьютере (64-битная архитектура Linux, длится 8-ядерная файловая система Intel Xeon, файловая система Luster и... надеюсь, хорошо сконфигурированные компиляторы)
(и я не объясняю причину "резонанса" для ручного буфера 1 КБ...)
ИЗМЕНИТЬ 2:
И резонанс на 1024 B (если у кого-то есть представление об этом, мне интересно):
![enter image description here]()
Ответы
Ответ 1
В основном это связано с накладными и косвенными вызовами функций. Метод ofstream:: write() наследуется от ostream. Эта функция не встроена в libstdС++, что является первым источником накладных расходов. Затем ostream:: write() должен вызвать rdbuf() → sputn() для выполнения собственно записи, которая является вызовом виртуальной функции.
Кроме того, libstdС++ перенаправляет sputn() на другую виртуальную функцию xsputn(), которая добавляет еще один вызов виртуальной функции.
Если вы помещаете символы в буфер самостоятельно, вы можете избежать этих накладных расходов.
Ответ 2
Я хотел бы объяснить причину пика на втором графике.
Фактически, виртуальные функции, используемые std::ofstream
, приводят к снижению производительности, как мы видим на первом рисунке, но это не дает ответа, почему самая высокая производительность была, когда размер ручного буфера был меньше 1024 байтов.
Проблема связана с высокой стоимостью системных вызовов writev()
и write()
и внутренней реализацией внутреннего класса std::filebuf
std::ofstream
.
Чтобы показать, как write()
влияет на производительность, я провел простой тест с использованием инструмента dd
на моей машине с Linux, чтобы скопировать 10 МБ файл с буфером разного размера (опция bs):
[email protected]$ time dd if=/dev/zero of=zero bs=256 count=40000
40000+0 records in
40000+0 records out
10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s
real 0m2.370s
user 0m0.000s
sys 0m0.952s
test$test: time dd if=/dev/zero of=zero bs=512 count=20000
20000+0 records in
20000+0 records out
10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s
real 0m1.324s
user 0m0.000s
sys 0m0.476s
[email protected]: time dd if=/dev/zero of=zero bs=1024 count=10000
10000+0 records in
10000+0 records out
10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s
real 0m0.798s
user 0m0.008s
sys 0m0.236s
[email protected]: time dd if=/dev/zero of=zero bs=4096 count=2500
2500+0 records in
2500+0 records out
10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s
real 0m0.293s
user 0m0.000s
sys 0m0.064s
Как вы можете видеть, чем меньше буфер, тем меньше скорость записи и тем больше времени dd
проводит в системном пространстве. таким образом, скорость чтения/записи уменьшается при уменьшении размера буфера.
Но почему самая высокая скорость была, когда размер ручного буфера был меньше 1024 байт в ручных тестах буфера создателя темы? Почему это было почти постоянно?
Объяснение относится к реализации std::ofstream
, особенно к std::basic_filebuf
.
По умолчанию используется буфер размером 1024 байта (переменная BUFSIZ). Таким образом, когда вы записываете данные, используя кусочки менее 1024, системный вызов writev()
(не write()
) вызывается как минимум один раз для двух операций ofstream::write()
(куски имеют размер 1023 & lt; 1024 - сначала записывается в буфера, а вторая заставляет писать первую и вторую). Исходя из этого, можно сделать вывод, что скорость ofstream::write()
не зависит от размера буфера вручную до пика (write()
вызывается как минимум дважды).
Когда вы пытаетесь записать больший или равный 1024 байта буфер сразу, используя вызов ofstream::write()
, системный вызов writev()
вызывается для каждого ofstream::write
. Итак, вы видите, что скорость увеличивается, когда ручной буфер больше 1024 (после пика).
Более того, если вы хотите установить буфер std::ofstream
больше, чем 1024 буфера (например, буфер 8192 байта), используя streambuf::pubsetbuf()
и вызвать ostream::write()
для записи данных, используя куски размером 1024, вы будете удивлены, что скорость записи будет быть таким же, как вы будете использовать 1024 буфера. Это связано с тем, что реализация std::basic_filebuf
- внутреннего класса std::ofstream
- жестко запрограммирована для принудительного вызова вызова системной writev()
для каждого вызова ofstream::write()
, когда переданный буфер является больше или равно 1024 байта (см. basic_filebuf :: xsputn() исходный код). Существует также открытая проблема в bugzilla GCC, о которой сообщалось в 2014-11-05.
Таким образом, решение этой проблемы может быть сделано в двух возможных случаях:
- замените
std::filebuf
вашим собственным классом и переопределите std::ofstream
- разделите буфер, который должен быть передан в
ofstream::write()
, на куски менее 1024 и передайте их в ofstream::write()
один за другим
- не передавайте небольшие фрагменты данных в
ofstream::write()
, чтобы избежать снижения производительности виртуальных функций std::ofstream
Ответ 3
Я бы хотел добавить к существующим ответам, что это поведение производительности (все накладные расходы от виртуальных вызовов/косвенных вызовов) обычно не является проблемой при написании больших блоков данных. То, что, как представляется, было исключено из вопроса, и эти предыдущие ответы (хотя, вероятно, неявно поняты), состоят в том, что исходный код записывал небольшое количество байтов каждый раз. Просто уточнить для других: если вы пишете большие блоки данных (~ kB+), нет причин ожидать, что буферизация вручную будет иметь значительную разницу в производительности при использовании буферизации std::fstream
.