Bardzo szybkie pisanie pliku binarnego w C++

Staram się zapisywać ogromne ilości danych na moim dysku SSD (solid state drive). A przez ogromne ilości mam na myśli 80GB.

Przeglądałem sieć w poszukiwaniu rozwiązań, ale najlepsze, co wymyśliłem, było to:

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

Skompilowany z Visual Studio 2010 i pełnymi optymalizacjami i uruchomiony pod Windows7 ten program osiąga maksimum około 20MB / s. co naprawdę mnie martwi, to to, że Windows może kopiować pliki z innego dysku SSD na ten dysk SSD z prędkością pomiędzy 150Mb/s a 200Mb / s. Więc co najmniej 7 razy szybciej. Dlatego ja myślę, że powinienem być w stanie jechać szybciej.

Jakieś pomysły, Jak mogę przyspieszyć pisanie?

Edit: teraz kompiluje.

Author: plasmacel, 2012-07-19

12 answers

This did the job:

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

Właśnie zmierzyłem 8GB w 36sec, czyli około 220mb / s i myślę, że to maksymalnie mój dysk SSD. Warto również zauważyć, że kod w pytaniu używał jednego rdzenia 100% , podczas gdy ten kod używa tylko 2-5%.

Dziękuję wszystkim.

Update : minęło 5 lat. Kompilatory, sprzęt, biblioteki i moje wymagania uległy zmianie. Dlatego wprowadziłem kilka zmian w kodzie i dokonałem kilku pomiarów.

Najpierw kod:

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

Teraz kod kompiluje się z Visual Studio 2017 i g++ 7.2.0 (który jest teraz jednym z moich wymagań). Pozwalam kodowi działać z dwoma ustawieniami:

  • Laptop, Core i7, SSD, Ubuntu 16.04, G++ Wersja 7.2.0 z -std=c++11-march=native-O3
  • System Windows 10, Visual Studio 2017 Wersja 15.3.1 z /Ox /Ob2 / Oi / Ot /GT /GL / Gy

Który dał następujące pomiary (po porzuceniu wartości dla 1MB, bo były oczywiste outliers): Tutaj wpisz opis obrazka Tutaj wpisz opis obrazka Oba razy option1 i option3 max out mój dysk SSD. Nie spodziewałem się, że to zobaczy, ponieważ option2 był najszybszym kodem na mojej maszynie.

TL; DR : moje pomiary wskazują na użycie std::fstream nad FILE.

 173
Author: Dominic Hofer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-20 22:59:52

Spróbuj wykonać następujące czynności w kolejności:

  • Mniejszy rozmiar bufora. Pisanie ~2 MiB na raz może być dobrym początkiem. Na moim ostatnim laptopie, ~ 512 KiB był słodki punkt, ale nie testowałem jeszcze na moim SSD.

    Uwaga: zauważyłem, że bardzo duże bufory mają tendencję do zmniejszania wydajności. Zauważyłem straty prędkości przy użyciu buforów 16-MiB zamiast buforów 512-KiB wcześniej.

  • Użyj _open (LUB _topen jeśli chcesz być Windows-correct), aby otworzyć Plik, następnie użyj _write. To pozwoli prawdopodobnie uniknąć wielu buforowania, ale nie jest pewne.

  • Korzystanie z funkcji specyficznych dla systemu Windows, takich jak CreateFile i WriteFile. Pozwoli to uniknąć buforowania w bibliotece standardowej.

 23
Author: Mehrdad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 16:09:48

Nie widzę różnicy między STD:: stream / FILE / device. Między buforowaniem a nie buforowaniem.

Uwaga:

    [[9]}dyski SSD "mają tendencję" do spowolnienia (niższe szybkości transferu), gdy się wypełniają.
  • dyski SSD "mają tendencję" do zwalniania (obniżania szybkości transferu) w miarę starzenia się (z powodu nie działających bitów).
Widzę, że kod działa w 63 sekundach.
Tak więc szybkość transferu wynosi: 260m / s (mój dysk SSD wygląda nieco szybciej niż twój).
64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

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

I get a brak zwiększenia przez przejście do pliku * z 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);

}

Więc strumień C++ działa tak szybko, jak pozwala na to podstawowa biblioteka.

Ale myślę, że to niesprawiedliwe porównywanie systemu operacyjnego do aplikacji, która jest zbudowana na OS. Aplikacja nie może wprowadzać żadnych założeń (nie wie, że dyski są SSD) i dlatego wykorzystuje mechanizmy plików systemu operacyjnego do przesyłania.

Podczas gdy system operacyjny nie musi podejmować żadnych założeń. Może określić rodzaje napędów i używać optymalna technika przesyłania danych. W tym przypadku bezpośredni transfer pamięci do pamięci. Spróbuj napisać program, który kopiuje 80G z jednego miejsca w pamięci do drugiego i zobacz, jak szybko to jest.

Edit

Zmieniłem mój kod, aby używać wywołań niższego poziomu:
ie bez buforowania.

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

Nie różniło się to od siebie.

Uwaga: mój dysk to dysk SSD jeśli masz normalny dysk, możesz zobaczyć różnicę między dwiema powyższymi technikami. Ale jak się spodziewałem nie buforowanie i buforowanie (przy zapisywaniu dużych kawałków większych niż rozmiar bufora) nie robią różnicy.

Edit 2:

Czy wypróbowałeś najszybszą metodę kopiowania plików w C++

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

    output << input.rdbuf();
}
 20
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 18:16:18

Najlepszym rozwiązaniem jest zaimplementowanie zapisu asynchronicznego z podwójnym buforowaniem.

Spójrz na linię czasową:

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

'F' oznacza czas wypełnienia bufora, a 'W' czas zapisu bufora na dysk. Więc problem w marnowaniu czasu między zapisaniem buforów do pliku. Jednak implementując zapis w osobnym wątku, możesz od razu zacząć wypełniać następny bufor w następujący sposób:

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

F-wypełnienie pierwszego bufora
f-wypełnienie 2nd bufor
W-pisanie 1. bufor do pliku
w-zapis drugiego bufora do pliku
_ - poczekaj na zakończenie operacji

To podejście ze swapami bufora jest bardzo przydatne, gdy wypełnienie bufora wymaga bardziej skomplikowanych obliczeń (stąd więcej czasu). Zawsze implementuję klasę CSequentialStreamWriter, która ukrywa wewnątrz zapis asynchroniczny, więc dla użytkownika końcowego interfejs ma tylko funkcję zapisu.

I rozmiar bufora musi być wielokrotnością rozmiaru klastra dysków. W przeciwnym razie skończysz z słaba wydajność poprzez zapis pojedynczego bufora do 2 sąsiednich klastrów dyskowych.

Zapisanie ostatniego bufora.
Podczas wywoływania funkcji Write po raz ostatni, musisz upewnić się, że bieżący bufor jest wypełniany powinien być również zapisany na dysk. Tak więc CSequentialStreamWriter powinien mieć osobną metodę, powiedzmy Finalize( final buffer flush), która powinna zapisać na dysk ostatnią część danych.

Obsługa błędów.
Podczas gdy kod zaczyna wypełniać drugi bufor, a Pierwszy z nich jest pisany w osobnym wątku, ale z jakiegoś powodu write nie powiedzie się, główny wątek powinien być świadomy tej awarii.

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

Załóżmy, że interfejs CSequentialStreamWriter ma funkcję Write zwraca bool lub rzuca wyjątek, więc mając błąd w oddzielnym wątku, musisz pamiętać o tym stanie, więc następnym razem, gdy wywołasz Write lub Finilize w głównym wątku, metoda zwróci False lub rzuci wyjątek. I nie ma znaczenia, w którym momencie przestałeś wypełniać bufor, nawet jeśli zapisałeś pewne dane przed awarią - najprawdopodobniej plik byłby uszkodzony i bezużyteczny.

 12
Author: HandMadeOX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-28 00:56:25

Proponuję spróbować mapowania plików . Używałem mmap w przeszłości, w środowisku uniksowym i byłem pod wrażeniem wysokiej wydajności, jaką mogłem osiągnąć

 10
Author: Ralph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 21:35:11

Czy mógłbyś zamiast tego użyć FILE* i zmierzyć osiągi, które uzyskałeś? Kilka opcji to użycie fwrite/write zamiast 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;
}

Jeśli zdecydujesz się użyć write, spróbuj czegoś podobnego:

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

Radziłbym Ci również przyjrzeć się memory map. To może być Twoja odpowiedź. Kiedyś musiałem przetworzyć plik 20GB w innym, aby zapisać go w bazie danych, a plik nawet się nie otwiera. Więc rozwiązanie, jak wykorzystać mapę moemory. Ale zrobiłem to w Python.

 7
Author: cybertextron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 15:55:57

Spróbuj użyć wywołań API open () / write () / close () i poeksperymentuj z rozmiarem bufora wyjściowego. Chodzi mi o to, aby nie przekazywać całego bufora "wiele-wiele-bajtów" na raz, zrobić kilka zapisów (tj. TotalNumBytes / OutBufferSize). OutBufferSize może wynosić od 4096 bajtów do megabajtów.

Kolejna próba-użyj WinAPI OpenFile / CreateFile i użyj tego artykułu MSDN , aby wyłączyć buforowanie (FILE_FLAG_NO_BUFFERING). I Ten artykuł MSDN na WriteFile () pokazuje jak uzyskać rozmiar bloku dla napęd, aby poznać optymalny rozmiar bufora.

W każdym razie, std:: ofstream jest opakowaniem i może być blokowanie operacji We / Wy. Należy pamiętać, że przemierzanie całej tablicy N-gigabajtów również zajmuje trochę czasu. Podczas pisania mały bufor, dostaje się do pamięci podręcznej i działa szybciej.

 6
Author: Viktor Latypov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 15:25:44

Spróbuj użyć plików mapowanych pamięcią.

 3
Author: qehgt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 15:43:08

Jeśli skopiujesz coś z dysku A na dysk B w Eksploratorze, Windows używa DMA. Oznacza to, że przez większość procesu kopiowania procesor w zasadzie nie robi nic innego niż mówi kontrolerowi dysku, gdzie umieścić i pobrać dane, eliminując cały krok w łańcuchu, i taki, który nie jest w ogóle zoptymalizowany do przenoszenia dużych ilości danych - I mam na myśli sprzęt.

Co ty robisz bardzo dużo dla procesora. Chcę wskazać ci część " niektóre obliczenia do wypełnienia []". Które Ja myśl jest niezbędna. Generujesz [], następnie kopiujesz Z [] do bufora wyjściowego( to robi fstream::write), następnie generujesz ponownie, itd.

Co robić? Wielowątkowość! (Mam nadzieję, że masz procesor wielordzeniowy)

    Widelec.
  • użyj jednego wątku do wygenerowania [] danych
  • użyj drugiego do zapisu danych z [] na dysk
  • będziesz potrzebował dwóch tablic a1 [] i a2 [] i przełączać się między nimi
  • będziesz potrzebował pewnego rodzaju synchronizacji między Twoim wątki (semafory, Kolejka komunikatów, itp.)
  • Użyj funkcji niższego poziomu, niebuforowanych, takich jak funkcja WriteFile wspomniana przez Mehrdad
 3
Author: dualed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 16:33:00

fstreams nie są wolniejsze niż strumienie C, ale używają więcej procesora (zwłaszcza jeśli buforowanie nie jest prawidłowo skonfigurowane). Gdy procesor się nasyci, ogranicza szybkość wejścia / Wyjścia.

Co najmniej implementacja MSVC 2015 kopiuje 1 znak na raz do bufora wyjściowego, gdy bufor strumienia nie jest ustawiony (zobacz streambuf::xsputn). Więc upewnij się, że ustawiłeś bufor strumienia (>0).

Mogę uzyskać prędkość zapisu 1500MB/s (pełna prędkość mojego dysku M. 2 SSD) z fstream używając tego kod:

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

Próbowałem tego kodu na innych platformach (Ubuntu, FreeBSD) i nie zauważyłem różnic szybkości wejścia/wyjścia, ale użycie CPU różnica około 8:1 (fstream używane 8 razy więcej CPU). Można więc sobie wyobrazić, że gdybym miał szybszy dysk, zapis fstream spowolniłby szybciej niż wersja stdio.

 3
Author: rustyx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-14 08:32:37

Jeśli chcesz szybko zapisywać strumienie plików, możesz zwiększyć bufor odczytu strumienia:

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

Ponadto, podczas zapisu dużej ilości danych do plików, czasami szybciej jestlogicznie rozszerzyć Rozmiar pliku zamiast fizycznie, dzieje się tak dlatego, że logicznie rozszerzając plik, system plików nie zeruje nowej przestrzeni przed zapisem do niego. Inteligentne jest również logiczne Rozszerzanie Pliku więcej niż jest to potrzebne, aby zapobiec wielu rozszerzeniom plików. Plik logiczny rozszerzenie jest obsługiwane w systemie Windows przez wywołanie SetFileValidData lub xfsctl z XFS_IOC_RESVSP64 w systemach XFS.

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-02 18:17:21

Kompiluję mój program w gcc w GNU / Linux i mingw w win 7 i win xp i działa dobrze

Możesz użyć mojego programu i aby utworzyć plik 80 GB wystarczy zmienić linię 33 na

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

Po wyjściu z programu plik zostanie zniszczony, a następnie sprawdź plik, gdy jest uruchomiony

Aby mieć program, który chcesz po prostu zmienić program

Pierwszy to program windows, a drugi dla GNU / Linux

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

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

 0
Author: MostafaJF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-16 02:14:43