Запись двоичного файла на C++ очень быстро

Я пытаюсь записать огромное количество данных на свой SSD (твердотельный диск). И под огромными суммами я имею в виду 80GB.

Я просмотрел интернет для решений, но лучшее, что я придумал, было следующее:

#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 где-то между 150MB/s и 200MB/s. Так по крайней мере 7 раз быстрее. Вот почему я думаю, что смогу двигаться быстрее.

есть идеи, как я могу ускорить мое письмо?

Edit: теперь он компилируется.

12 ответов


Это сделало работу:

#include <stdio.h>
const unsigned long long size = 8ULL*1024ULL*1024ULL;
unsigned long long a[size];

int main()
{
    FILE* pFile;
    pFile = fopen("file.binary", "wb");
    for (unsigned long long j = 0; j < 1024; ++j){
        //Some calculations to fill a[]
        fwrite(a, 1, size*sizeof(unsigned long long), pFile);
    }
    fclose(pFile);
    return 0;
}

Я только что приурочил 8GB в 36sec, что составляет около 220MB / s, и я думаю, что maxes из моего SSD. Также стоит отметить, что код в вопросе использовал одно ядро 100%, тогда как этот код использует только 2-5%.

большое спасибо всем.

обновление: прошло 5 лет. Компиляторы, оборудование, библиотеки и мои требования изменились. Вот почему я внес некоторые изменения в код и сделал некоторые измерения.

первый код:

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>

std::vector<uint64_t> GenerateData(std::size_t bytes)
{
    assert(bytes % sizeof(uint64_t) == 0);
    std::vector<uint64_t> data(bytes / sizeof(uint64_t));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
    return data;
}

long long option_1(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_2(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    FILE* file = fopen("file.binary", "wb");
    fwrite(&data[0], 1, bytes, file);
    fclose(file);
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_3(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    std::ios_base::sync_with_stdio(false);
    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

int main()
{
    const std::size_t kB = 1024;
    const std::size_t MB = 1024 * kB;
    const std::size_t GB = 1024 * MB;

    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl;

    return 0;
}

теперь код компилируется с Visual Studio 2017 и g++ 7.2.0 (что теперь является одним из моих требований). Я позволил коду работать с двумя настройками:

  • ноутбук, Core i7, SSD, Ubuntu 16.04, G++ версия 7.2.0 с-std=c++11-march=native-O3
  • рабочий стол, Core i7, SSD, Windows 10, Visual Studio 2017 версии 15.3.1 с /Ox /Ob2 /Oi /Ot /GT /GL / Gy

, который дал после измерений (после канализации значений для 1MB, потому что они были очевидными выбросами): enter image description here enter image description here Оба раза option1 и option3 max из моего SSD. Я не ожидал, что это увидит, потому что option2 раньше был самым быстрым кодом на моей машине.

TL; DR: мои измерения указывают на использование std::fstream над FILE.


попробуйте следующее, по порядку:

  • меньше размер буфера. Записи ~2 Мб за раз может быть хорошим началом. На моем последнем ноутбуке ~512 KiB был сладким местом,но я еще не тестировал на своем SSD.

    Примечание: я заметил, что очень большие буферы, как правило,уменьшить производительность. Я заметил потери скорости с использованием буферов 16-MiB вместо буферов 512-KiB раньше.

  • использовать _open (или _topen Если вы хотите быть Windows-правильно), чтобы открыть файл, используйте _write. Это будет наверное избежать много буферизации, но это не обязательно.

  • использование специфичных для Windows функций, таких как CreateFile и WriteFile. Это позволит избежать буферизации в стандартной библиотеке.


Я не вижу разницы между std:: stream/FILE/device. Между буферизацией и не буферизацией.

обратите внимание:

  • SSD-накопители "имеют тенденцию" замедляться (более низкие скорости передачи) по мере их заполнения.
  • SSD-накопители "имеют тенденцию" замедляться (более низкие скорости передачи) по мере старения (из-за нерабочих битов).

Я вижу код, работать в 63 secondds.
Таким образом, скорость передачи: 260M / s (мой SSD выглядит немного быстрее твоего).

64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

= 16G
= 16G/63 = 260M/s

Я не получаю увеличения, перемещаясь в файл * из std:: fstream.

#include <stdio.h>

using namespace std;

int main()
{

    FILE* stream = fopen("binary", "w");

    for(int loop=0;loop < 32;++loop)
    {
         fwrite(a, sizeof(unsigned long long), size, stream);
    }
    fclose(stream);

}

таким образом, поток C++ работает так быстро, как позволяет базовая библиотека.

но я думаю, что несправедливо сравнивать ОС с приложением, которое построено поверх ОС. Приложение не может делать никаких предположений (оно не знает, что диски SSD) и, таким образом, использует файловые механизмы ОС для передачи.

пока ОС не нужна делать какие-либо предположения. Он может сказать типы приводов, участвующих и использовать оптимальную технику для передачи данных. В этом случае прямая передача памяти в память. Попробуйте написать программу, которая копирует 80G из 1 места в памяти в другое и посмотреть, насколько это быстро.

редактировать

Я изменил свой код, чтобы использовать вызовы нижнего уровня:
т. е. без буферизации.

#include <fcntl.h>
#include <unistd.h>


const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    int data = open("test", O_WRONLY | O_CREAT, 0777);
    for(int loop = 0; loop < 32; ++loop)
    {   
        write(data, a, size * sizeof(unsigned long long));
    }   
    close(data);
}

это не diffference.

Примечание: Мой диск-это SSD диск, если у вас нормальный диск, вы можете увидеть разницу между двумя методами. Но, как я и ожидал, не буферизация и буферизация (при записи больших кусков больше размера буфера) не имеют никакого значения.

Edit 2:

вы пробовали самый быстрый способ копирования файлов на C++

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}

лучшим решением является реализация асинхронной записи с двойной буферизацией.

посмотрите на линию времени:

------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|

'F' представляет время для заполнения буфера, а ' W ' представляет время для записи буфера на диск. Таким образом, проблема в трате времени между написанием буферов в файл. Однако, реализуя запись в отдельном потоке, вы можете сразу начать заполнять следующий буфер следующим образом:

------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
  |WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|

F-заполнение 1-го буфера
Ф - заполнять 2-й буфер
W-запись 1-го буфера в файл
W-запись 2-го буфера в файл
_ - подождите, пока операция будет завершена

этот подход с буферными свопами очень полезен, когда заполнение буфера требует более сложных вычислений (следовательно, больше времени). Я всегда реализую класс CSequentialStreamWriter, который скрывает асинхронную запись внутри, поэтому для конечного пользователя интерфейс имеет только функцию(ы) записи.

и размер буфера должен быть кратен размер кластера диска. В противном случае вы получите низкую производительность, написав один буфер на 2 смежных дисковых кластера.

запись последнего буфера.
При вызове функции Write в последний раз необходимо убедиться, что текущий буфер заполняется и должен быть записан на диск. Таким образом, CSequentialStreamWriter должен иметь отдельный метод, скажем Finalize (final buffer flush), который должен записывать на диск последнюю часть данных.

ошибка обращение.
В то время как код начинает заполнять 2-й буфер, а 1-й записывается в отдельный поток, но по какой-то причине запись не выполняется, основной поток должен знать об этом сбое.

------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|

предположим, что интерфейс CSequentialStreamWriter имеет функцию Write возвращает bool или выдает исключение, таким образом, имея ошибку в отдельном потоке, вы должны помнить это состояние, поэтому в следующий раз, когда вы вызовете Write или Finilize в основном потоке, метод вернется False или выдаст исключение. И не имеет значения, в какой момент Вы перестали заполнять буфер, даже если вы написали некоторые данные вперед после сбоя - скорее всего, файл будет поврежден и бесполезен.


Я бы предложил попробовать картография. Я использовал mmapв прошлом, в среде UNIX, и я был впечатлен высокой производительностью, которую я мог достичь


не могли бы вы использовать FILE* вместо этого, и измерить производительность, вы получили? Несколько вариантов-использовать fwrite/write вместо fstream:

#include <stdio.h>

int main ()
{
  FILE * pFile;
  char buffer[] = { 'x' , 'y' , 'z' };
  pFile = fopen ( "myfile.bin" , "w+b" );
  fwrite (buffer , 1 , sizeof(buffer) , pFile );
  fclose (pFile);
  return 0;
}

если вы решите использовать write попробуйте что-то подобное:

#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int filedesc = open("testfile.txt", O_WRONLY | O_APPEND);

    if (filedesc < 0) {
        return -1;
    }

    if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) {
        write(2, "There was an error writing to testfile.txt\n", 43);
        return -1;
    }

    return 0;
}

Я бы также посоветовал вам заглянуть в memory map. Это может быть вашим ответом. Однажды мне пришлось обрабатывать файл 20GB в другом, чтобы сохранить его в базе данных, а файл даже не открывался. Таким образом, решение использовать карту moemory. Я сделал это в Python хотя.


попробуйте использовать вызовы API open()/write()/close() и поэкспериментировать с размером выходного буфера. Я имею в виду, не передавайте весь буфер "много-много-байт" сразу, сделайте пару записей (т. е. TotalNumBytes / OutBufferSize). OutBufferSize может быть от 4096 байт до мегабайта.

еще одна попытка-используйте WinAPI OpenFile / CreateFile и используйте эта статья MSDN отключить буферизацию (file_flag_no_buffering без кэширования). И эта статья MSDN о WriteFile () показывает, как получить размер блока для привода, чтобы знать оптимальный размер буфера.

в любом случае, std::ofstream-это оболочка, и может быть блокировка операций ввода-вывода. Имейте в виду, что прохождение всего массива N-gigabyte также занимает некоторое время. Пока вы пишете небольшой буфер, он попадает в кэш, и работает быстрее.


попробуйте использовать отображаемые в память файлы.


Если вы копируете что-то с диска A на диск B в проводнике, Windows использует DMA. Это означает, что для большей части процесса копирования процессор в основном не будет делать ничего, кроме указания контроллеру диска, куда поместить и получить данные, исключая целый шаг в цепочке, и тот, который вообще не оптимизирован для перемещения больших объемов данных - и я имею в виду аппаратное обеспечение.

Что вы do включает в себя CPU много. Я хочу указать вам на "некоторые расчеты для заполнения[]" часть. Что я считаю необходимым. Вы генерируете [], затем копируете из[] в выходной буфер (это то, что делает fstream::write), затем вы генерируете снова и т. д.

Что делать? Многопоточность! (Надеюсь, у вас есть многоядерный процессор)

  • вилка.
  • используйте один поток для создания [] data
  • используйте другой для записи данных из[] на диск
  • вам понадобятся два массива a1[] и a2[] и переключение между ними
  • вы нужна какая-то синхронизация между потоками (семафоры, очереди сообщений и т. д.)
  • используйте более низкий уровень, unbuffered, функции, как WriteFile функция, упомянутая Mehrdad

fstreams не медленнее, чем потоки C, как таковые, но они используют больше CPU (особенно если буферизация настроена неправильно). Когда процессор насыщается, он ограничивает скорость ввода-вывода.

по крайней мере, копии реализации MSVC 2015 1 символ за раз к выходному буферу, когда буфер потока не установлен (см. streambuf::xsputn). Так что убедитесь, что установлен буфер потока (>0).

Я могу получить скорость записи 1500MB / s (полная скорость моего M. 2 SSD) с fstream используя этот код:

#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#endif
using namespace std;
using namespace std::chrono;
const size_t sz = 512 * 1024 * 1024;
const int numiter = 20;
const size_t bufsize = 1024 * 1024;
int main(int argc, char**argv)
{
  unique_ptr<char[]> data(new char[sz]);
  unique_ptr<char[]> buf(new char[bufsize]);
  for (size_t p = 0; p < sz; p += 16) {
    memcpy(&data[p], "BINARY.DATA.....", 16);
  }
  unlink("file.binary");
  int64_t total = 0;
  if (argc < 2 || strcmp(argv[1], "fopen") != 0) {
    cout << "fstream mode\n";
    ofstream myfile("file.binary", ios::out | ios::binary);
    if (!myfile) {
      cerr << "open failed\n"; return 1;
    }
    myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      myfile.write(data.get(), sz);
      if (!myfile)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    myfile.close();
  }
  else {
    cout << "fopen mode\n";
    FILE* pFile = fopen("file.binary", "wb");
    if (!pFile) {
      cerr << "open failed\n"; return 1;
    }
    setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important
    auto tm1 = high_resolution_clock::now();
    for (int i = 0; i < numiter; ++i) {
      auto tm1 = high_resolution_clock::now();
      if (fwrite(data.get(), sz, 1, pFile) != 1)
        cerr << "write failed\n";
      auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
      cout << tm << " ms\n";
      total += tm;
    }
    fclose(pFile);
    auto tm2 = high_resolution_clock::now();
  }
  cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n";
}

Я пробовал этот код на других платформах (Ubuntu, FreeBSD) и не заметил различий в скорости ввода-вывода, но загрузка ЦП разница около 8: 1 (fstream используется 8 раз больше CPU). Так что можно себе представить, будь у меня более быстрый диск,fstream запись замедлится раньше, чем stdio версия.


если вы хотите быстро писать в файловые потоки, вы можете сделать поток буфера чтения больше:

wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);

кроме того, при записи большого количества данных в файлы иногда быстрее логически расширение размера файла вместо физического, это потому, что при логическом расширении файла файловая система не обнуляет новое пространство перед записью в него. Также разумно логически расширить файл больше, чем вам действительно нужно, чтобы предотвратить множество расширений файлов. Расширение логического файла поддерживается в Windows с помощью вызова SetFileValidData или xfsctl С XFS_IOC_RESVSP64 на системах XFS.


im компиляция моей программы в gcc в GNU / Linux и mingw в win 7 и win xp и работал хорошо

вы можете использовать мою программу и создать файл 80 GB, просто измените строку 33 на

makeFile("Text.txt",1024,8192000);

при выходе из программы файл будет уничтожен, затем проверьте файл, когда он работает

чтобы иметь программу, которую вы хотите, просто измените программу

firt one-это программа для windows, а вторая-для GNU / Linux

http://mustafajf.persiangig.com/Projects/File/WinFile.cpp

http://mustafajf.persiangig.com/Projects/File/File.cpp