Я пытаюсь записать огромное количество данных на мой SSD (твердотельный накопитель). И в огромных количествах я имею в виду 80 ГБ.
Я просматривал в Интернете решения, но лучшее, что я придумал, было это:
#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
std::fstream myfile;
myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
//Here would be some error handling
for(int i = 0; i < 32; ++i){
//Some calculations to fill a[]
myfile.write((char*)&a,size*sizeof(unsigned long long));
}
myfile.close();
}
Скомпилированная с Visual Studio 2010 с полной оптимизацией и работающая под Windows7, эта программа работает со скоростью около 20 МБ / с. Что действительно беспокоит меня, так это то, что Windows может копировать файлы с другого SSD на этот SSD со скоростью от 150 МБ / с до 200 МБ / с. Так как минимум в 7 раз быстрее. Вот почему я думаю, что я должен быть в состоянии идти быстрее.
Любые идеи, как я могу ускорить свое письмо?
c++
performance
optimization
file-io
io
Доминик Хофер
источник
источник
fwrite()
я мог получить около 80% пиковых скоростей записи. Только с этимFILE_FLAG_NO_BUFFERING
я смог достичь максимальной скорости.Ответы:
Это сделал работу (в 2012 году):
Я только что рассчитал 8 ГБ за 36 секунд, что составляет около 220 МБ / с, и я думаю, что это максимум моего SSD. Также стоит отметить, что код в вопросе использовал одно ядро 100%, тогда как этот код использует только 2-5%.
Большое спасибо всем.
Обновление : прошло 5 лет, сейчас 2017. Компиляторы, оборудование, библиотеки и мои требования изменились. Вот почему я внес некоторые изменения в код и сделал несколько новых измерений.
Сначала код:
Этот код компилируется с Visual Studio 2017 и g ++ 7.2.0 (новые требования). Я запустил код с двумя настройками:
Что дало следующие измерения (после сброса значений для 1 МБ, потому что они были очевидными выбросами): оба варианта option1 и option3 максимально превышают мой SSD. Я не ожидал, что это увидит, потому что option2 раньше был самым быстрым кодом на моей старой машине.
TL; DR : мои измерения указывают на использование
std::fstream
болееFILE
.источник
FILE*
быстрее, чем потоки. Я бы не ожидал такой разницы, так как она все равно должна была быть связана с вводом / выводом.ios::sync_with_stdio(false);
какое-либо значение для кода с потоком? Мне просто любопытно, насколько велика разница между использованием этой строки и нет, но у меня нет достаточно быстрого диска для проверки углового случая. И если есть какая-то реальная разница.Попробуйте следующее по порядку:
Меньший размер буфера. Написание ~ 2 MiB за раз может быть хорошим началом. На моем последнем ноутбуке ~ 512 КиБ было приятным местом, но я еще не тестировал свой SSD.
Примечание: я заметил, что очень большие буферы имеют тенденцию снижать производительность. Я заметил потерю скорости при использовании буферов по 16 МБ вместо буферов по 512 КБ.
Используйте
_open
(или_topen
если вы хотите, чтобы Windows-корректно), чтобы открыть файл, затем используйте_write
. Это, вероятно, позволит избежать большой буферизации, но это не обязательно.Использование специфичных для Windows функций, таких как
CreateFile
иWriteFile
. Это позволит избежать буферизации в стандартной библиотеке.источник
FILE_FLAG_NO_BUFFERING
буферы большего размера. Так как я думаюFILE_FLAG_NO_BUFFERING
это в значительной степени DMA.Я не вижу разницы между std :: stream / FILE / device. Между буферизацией и без буферизации.
Также обратите внимание:
Я вижу код запускается в 63 секунды.
Таким образом скорость передачи: 260M / s (мой SSD выглядит немного быстрее, чем ваш).
Я не получаю увеличения при переходе на FILE * из std :: fstream.
Таким образом, поток C ++ работает настолько быстро, насколько позволяет соответствующая библиотека.
Но я думаю, что несправедливо сравнивать ОС с приложением, созданным поверх ОС. Приложение не может делать никаких предположений (оно не знает, что диски являются SSD) и, таким образом, использует файловые механизмы ОС для передачи.
Пока ОС не нужно делать никаких предположений. Он может определять типы используемых дисков и использовать оптимальную технику для передачи данных. В этом случае прямая передача из памяти в память. Попробуйте написать программу, которая копирует 80G из одного места в памяти в другое и посмотрите, насколько это быстро.
редактировать
Я изменил свой код, чтобы использовать вызовы более низкого уровня:
т.е. без буферизации.
Это не имеет никакого значения.
ПРИМЕЧАНИЕ . Мой диск является диском SSD. Если у вас обычный диск, вы можете увидеть разницу между двумя методами, описанными выше. Но, как я и ожидал, не буферизация и буферизация (при записи больших кусков, превышающих размер буфера) не имеют значения.
Изменить 2:
Вы пробовали самый быстрый способ копирования файлов в C ++
источник
FILE*
.Лучшее решение - реализовать асинхронную запись с двойной буферизацией.
Посмотрите на временную шкалу:
«F» представляет время для заполнения буфера, а «W» представляет время для записи буфера на диск. Так что проблема в том, чтобы тратить время между записью буферов в файл. Однако, реализовав запись в отдельном потоке, вы можете сразу же начать заполнение следующего буфера следующим образом:
F - заполнение 1-го буфера
f - заполнение 2-го буфера
W - запись 1-го буфера в файл
w - запись 2-го буфера в файл
_ - ожидание завершения операции
Этот подход с заменой буфера очень полезен, когда заполнение буфера требует более сложных вычислений (следовательно, больше времени). Я всегда реализую класс CSequentialStreamWriter, который скрывает асинхронную запись внутри, поэтому для конечного пользователя интерфейс имеет только функцию Write.
И размер буфера должен быть кратным размеру дискового кластера. В противном случае вы получите низкую производительность, записав один буфер в 2 смежных дисковых кластера.
Запись последнего буфера.
Когда вы в последний раз вызываете функцию Write, вы должны убедиться, что текущий буфер заполняется, и на диск также записывается. Таким образом, CSequentialStreamWriter должен иметь отдельный метод, скажем, Finalize (финальная очистка буфера), который должен записывать на диск последнюю часть данных.
Обработка ошибок.
Хотя код начинает заполнять 2-й буфер, а 1-й пишется в отдельном потоке, но по какой-то причине запись не удалась, основной поток должен знать об этом сбое.
Давайте предположим, что интерфейс CSequentialStreamWriter имеет функцию Write, возвращает bool или выдает исключение, поэтому при наличии ошибки в отдельном потоке вы должны запомнить это состояние, поэтому в следующий раз, когда вы вызовете Write или Finilize в основном потоке, метод вернет Ложь или бросит исключение. И на самом деле не имеет значения, в какой момент вы перестали заполнять буфер, даже если вы записали некоторые данные после сбоя - скорее всего, файл будет поврежден и бесполезен.
источник
Я бы предложил попробовать сопоставление файлов . Я использовал
mmap
в прошлом в среде UNIX, и я был впечатлен высокой производительностью, которую я смог достичьисточник
Можете ли вы использовать
FILE*
вместо этого, и меру производительности, которую вы получили? Несколько вариантов использоватьfwrite/write
вместоfstream
:Если вы решили использовать
write
, попробуйте нечто подобное:Я бы также посоветовал вам разобраться
memory map
. Это может быть ваш ответ. Однажды мне пришлось обработать файл размером 20 ГБ в другом, чтобы сохранить его в базе данных, и файл даже не открывался. Так что решение как использовать карту памяти. Я сделал это вPython
то же время.источник
FILE*
эквивалент исходного кода, использующего тот же самый буфер 512 МБ, получает полную скорость. Ваш текущий буфер слишком мал.2
соответствует стандартной ошибке, но все равно рекомендуется использоватьSTDERR_FILENO
вместо2
. Другая важная проблема заключается в том, что одним из возможных ошибок, который вы можете получить, является EINTR, когда вы получаете сигнал прерывания, это не настоящая ошибка, и вам следует просто повторить попытку.Попробуйте использовать вызовы API open () / write () / close () и поэкспериментируйте с размером выходного буфера. Я имею в виду, не передавайте весь буфер "много-много байтов" сразу, сделайте пару записей (т.е. TotalNumBytes / OutBufferSize). OutBufferSize может быть от 4096 байтов до мегабайта.
Еще одна попытка - используйте WinAPI OpenFile / CreateFile и используйте эту статью MSDN для отключения буферизации (FILE_FLAG_NO_BUFFERING). И эта статья MSDN о WriteFile () показано, как получить размер блока, чтобы накопитель знал оптимальный размер буфера.
В любом случае, std :: ofstream является оболочкой и может быть блокировка операций ввода-вывода. Имейте в виду, что обход всего массива N-гигабайт также занимает некоторое время. Пока вы пишете небольшой буфер, он попадает в кеш и работает быстрее.
источник
fstream
По сути, они не медленнее, чем C-потоки, но они используют больше ЦП. (особенно если буферизация настроена неправильно). Когда процессор насыщается, он ограничивает скорость ввода-вывода.По крайней мере, реализация MSVC 2015 копирует 1 символ за раз в буфер вывода, когда буфер потока не установлен (см.
streambuf::xsputn
). Поэтому не забудьте установить буфер потока (> 0) .Я могу получить скорость записи 1500 МБ / с (полная скорость моего M.2 SSD) с
fstream
помощью этого кода:Я попробовал этот код на других платформах (Ubuntu, FreeBSD) и не заметил различий в скорости ввода-вывода, но разница в использовании процессора составляет около 8: 1 (
fstream
используется в 8 раз больше процессора ). Так что можно себе представить, если бы у меня был более быстрый диск,fstream
запись замедлялась бы быстрее, чемstdio
версия.источник
Если вы копируете что-то с диска A на диск B в проводнике, Windows использует DMA. Это означает, что для большей части процесса копирования ЦП в основном не будет делать ничего, кроме указания дисковому контроллеру, куда помещать и получать данные, исключая целый шаг в цепочке и тот, который вообще не оптимизирован для перемещения больших объемов данных - и я имею в виду оборудование.
То, что вы делаете, требует много ресурсов процессора. Я хочу указать вам на «Некоторые вычисления, чтобы заполнить []» часть. Что я считаю необходимым. Вы генерируете [], затем копируете из [] в выходной буфер (это делает fstream :: write), затем генерируете снова и т. Д.
Что делать? Многопоточность! (Надеюсь у вас многоядерный процессор)
источник
Попробуйте использовать отображенные в памяти файлы.
источник
ReadFile
и для последовательного доступа, хотя для произвольного доступа они вполне могут быть лучше.Если вы хотите быстро записывать в файловые потоки, вы можете увеличить поток для буфера чтения:
Кроме того, при записи большого количества данных в файлы иногда быстрее логически расширить размер файла, чем физически, это происходит потому, что при логическом расширении файла файловая система не обнуляет новое пространство перед записью в него. Также разумно расширять файл больше, чем нужно, чтобы предотвратить множество расширений файлов. Расширение логического файла поддерживается в Windows путем вызова
SetFileValidData
илиxfsctl
с помощьюXFS_IOC_RESVSP64
систем XFS.источник
я компилирую мою программу в gcc в GNU / Linux и mingw в win 7 и win xp и работала хорошо
Вы можете использовать мою программу и создать файл размером 80 ГБ, просто измените строку 33 на
при выходе из программы файл будет уничтожен, затем проверьте файл, когда он работает
иметь программу, которую вы хотите просто изменить программу
Первая - программа для Windows, вторая - для GNU / Linux.
http://mustafajf.persiangig.com/Projects/File/WinFile.cpp
http://mustafajf.persiangig.com/Projects/File/File.cpp
источник