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

Я пытаюсь записать огромное количество данных на мой 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 раз быстрее. Вот почему я думаю, что я должен идти быстрее.

Любые идеи, как я могу ускорить свое письмо?

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

Ответ 1

Это выполнило задание:

#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;
}

Я просто приурочил 8 ГБ в 36сек, что составляет около 220 Мбайт/с, и я думаю, что это максимальный размер моего 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 = С++ 11 -march = native -O3
  • Рабочий стол, Core i7, SSD, Windows 10, Visual Studio 2017 Версия 15.3.1 с /Ox/Ob 2/Oi/Ot/GT/GL/Gy

Который дал следующие измерения (после отбрасывания значений для 1 МБ, поскольку они были очевидными выбросами): введите описание изображения здесь введите описание изображения здесь Оба варианта option1 и option3 максимизируют мой SSD. Я не ожидал, что это увидит, потому что option2 раньше был самым быстрым кодом на моей машине.

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

Ответ 2

Попробуйте следующее:

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

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

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

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

Ответ 3

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

Также обратите внимание:

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

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

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

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

У меня нет увеличения, перейдя в FILE * из 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);

}

Итак, поток С++ работает так же быстро, как позволяет базовая библиотека.

Но я считаю несправедливым сравнение ОС с приложением, которое построено на вершине ОС. Приложение не может делать никаких предположений (он не знает, что диски являются 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);
}

Это не делало различий.

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

Изменить 2:

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

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

    output << input.rdbuf();
}

Ответ 4

Лучшее решение - реализовать асинхронную запись с двойной буферизацией.

Посмотрите на строку времени:

------------------------------------------------>
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-го буфера
f - заполнение второго буфера
W - запись 1-го буфера в файл
w - запись второго буфера в файл
_ - дождаться завершения операции

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

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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

Не могли бы вы использовать 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. Это может быть ваш ответ. Однажды мне пришлось обработать 20-гигабайтный файл в другом, чтобы сохранить его в базе данных, и файл, который даже не открывается. Таким образом, решение использовать монемографическую карту. Я сделал это в Python, хотя.

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

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

Я могу получить скорость записи 1500 МБ/с (полная скорость моего 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 раз больше ЦП). Поэтому можно представить, что если бы у меня был более быстрый диск, запись fstream замедлялась раньше, чем версия stdio.

Ответ 11

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

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

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

Ответ 12

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

вы можете использовать мою программу и создать файл размером 80 ГБ, просто изменив строку 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