Подтвердить что ты не робот

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 и... надеюсь, хорошо сконфигурированные компиляторы) benchmark (и я не объясняю причину "резонанса" для ручного буфера 1 КБ...)

ИЗМЕНИТЬ 2: И резонанс на 1024 B (если у кого-то есть представление об этом, мне интересно): enter image description here

4b9b3361

Ответ 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.