Próbuję zapisać ogromne ilości danych na moim dysku SSD (dysk SSD). I przez ogromne kwoty mam na myśli 80 GB.
Przeglądałem sieć w poszukiwaniu rozwiązań, ale najlepsze, jakie wymyśliłem, 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 działający pod Windows7 ten program osiąga maksimum około 20 MB / s. Naprawdę przeszkadza mi to, że Windows może kopiować pliki z innego dysku SSD na ten dysk SSD w zakresie od 150 MB / s do 200 MB / s. Co najmniej 7 razy szybciej. Dlatego uważam, że powinienem być w stanie jechać szybciej.
Jakieś pomysły, jak mogę przyspieszyć pisanie?
c++
performance
optimization
file-io
io
Dominic Hofer
źródło
źródło
fwrite()
, mogłem uzyskać około 80% maksymalnej prędkości zapisu. Tylko dziękiFILE_FLAG_NO_BUFFERING
nie mogłem uzyskać maksymalnej prędkości.Odpowiedzi:
Wykonało to zadanie (w 2012 r.):
Właśnie taktowałem 8 GB w 36 sekund, czyli około 220 MB / s, i myślę, że to maksymalnie zwiększa pojemność mojego dysku SSD. Warto również zauważyć, że kod w pytaniu używał jednego rdzenia 100%, podczas gdy ten kod używa tylko 2-5%.
Wielkie dzięki wszystkim.
Aktualizacja : 5 lat minęło już w 2017 roku. Kompilatory, sprzęt, biblioteki i moje wymagania uległy zmianie. Dlatego wprowadziłem pewne zmiany w kodzie i dokonałem kilku nowych pomiarów.
Najpierw kod:
Ten kod kompiluje się z Visual Studio 2017 i g ++ 7.2.0 (nowe wymagania). Uruchomiłem kod z dwoma ustawieniami:
Co dało następujące pomiary (po zrzuceniu wartości dla 1 MB, ponieważ były to oczywiste wartości odstające): Zarówno razy opcja 1, jak i opcja 3 maksymalizowały mój dysk SSD. Nie spodziewałem się tego, ponieważ opcja 2 była wtedy najszybszym kodem na mojej starej maszynie.
TL; DR : Moje pomiary wskazują użycie
std::fstream
przezFILE
.źródło
FILE*
jest szybszy niż strumienie. Nie spodziewałbym się takiej różnicy, ponieważ i tak powinien był być związany z operacjami we / wy.ios::sync_with_stdio(false);
robi jakąkolwiek różnicę w kodzie ze strumieniem? Jestem ciekawy, jak duża jest różnica między używaniem tej linii, a nie, ale nie mam wystarczająco szybkiego dysku, aby sprawdzić skrzynkę narożną. A jeśli jest jakaś prawdziwa różnica.Spróbuj wykonać następujące czynności:
Mniejszy rozmiar bufora. Pisanie ~ 2 MiB na raz może być dobrym początkiem. Na moim ostatnim laptopie było ~ 512 KiB, ale nie testowałem jeszcze na moim dysku SSD.
Uwaga: zauważyłem, że bardzo duże bufory mają tendencję do zmniejszania wydajności. Zauważyłem wcześniej straty prędkości przy użyciu buforów 16-MiB zamiast buforów 512-KiB.
Użyj
_open
(lub_topen
jeśli chcesz mieć poprawny system Windows), aby otworzyć plik, a następnie użyj_write
. Będzie to prawdopodobnie uniknąć wielu buforowania, ale nie jest to pewne.Korzystanie z funkcji specyficznych dla systemu Windows, takich jak
CreateFile
iWriteFile
. Pozwoli to uniknąć buforowania w standardowej bibliotece.źródło
FILE_FLAG_NO_BUFFERING
- w której większe bufory wydają się być lepsze. Ponieważ myślę, żeFILE_FLAG_NO_BUFFERING
to właściwie DMA.Nie widzę różnicy między std :: stream / FILE / device. Między buforowaniem a brakiem buforowania.
Uwaga:
Widzę, że kod działa w 63 sekundy.
Zatem szybkość transferu: 260 M / s (mój dysk SSD wygląda nieco szybciej niż twój).
Nie otrzymuję wzrostu, przechodząc do FILE * ze std :: fstream.
Tak więc strumień C ++ działa tak szybko, jak na to pozwala podstawowa biblioteka.
Ale myślę, że niesprawiedliwe jest porównywanie systemu operacyjnego z aplikacją zbudowaną na systemie operacyjnym. Aplikacja nie może przyjmować żadnych założeń (nie wie, że dyski są dyskami SSD), dlatego do przesyłania używa mechanizmów plików systemu operacyjnego.
Chociaż system operacyjny nie musi przyjmować żadnych założeń. Potrafi określić typy zaangażowanych napędów i zastosować optymalną technikę przesyłania danych. W tym przypadku bezpośredni transfer z pamięci do pamięci. Spróbuj napisać program, który kopiuje 80G z jednej lokalizacji w pamięci do innej i sprawdź, jak szybko to jest.
Edytować
Zmieniłem kod, aby korzystać z wywołań niższego poziomu:
tj. Bez buforowania.
Nie miało to żadnej różnicy.
UWAGA : Mój dysk jest dyskiem SSD, jeśli masz normalny dysk, możesz zauważyć różnicę między dwiema powyższymi technikami. Ale, jak się spodziewałem, buforowanie i buforowanie (przy zapisywaniu dużych fragmentów większych niż rozmiar bufora) nie ma znaczenia.
Edycja 2:
Czy wypróbowałeś najszybszą metodę kopiowania plików w C ++
źródło
FILE*
.Najlepszym rozwiązaniem jest zaimplementowanie zapisu asynchronicznego z podwójnym buforowaniem.
Spójrz na linię czasu:
„F” oznacza czas wypełnienia bufora, a „W” oznacza czas zapisania bufora na dysk. Problem marnotrawstwa czasu między zapisywaniem buforów do pliku. Jednak wdrażając pisanie w osobnym wątku, możesz od razu zacząć wypełniać następny bufor:
F - wypełnienie 1. bufora
f - wypełnienie 2. bufora
W - zapisanie 1. bufora do pliku
w - zapisanie 2. bufora do pliku
_ - poczekaj na zakończenie operacji
To podejście z zamianą buforów jest bardzo przydatne, gdy wypełnienie bufora wymaga bardziej złożonych obliczeń (stąd więcej czasu). Zawsze implementuję klasę CSequentialStreamWriter, która ukrywa w sobie zapis asynchroniczny, więc dla użytkownika końcowego interfejs ma tylko funkcje zapisu.
Rozmiar bufora musi być wielokrotnością rozmiaru klastra dysku. W przeciwnym razie skończysz na niskiej wydajności, pisząc jeden bufor do 2 sąsiednich klastrów dyskowych.
Zapis ostatniego bufora.
Kiedy wywołujesz funkcję Write po raz ostatni, musisz upewnić się, że bieżący bufor jest zapełniony, również powinien zostać zapisany na dysk. Zatem CSequentialStreamWriter powinien mieć osobną metodę, powiedzmy Finalize (końcowe opróżnianie bufora), która powinna zapisywać na dysku ostatnią porcję danych.
Obsługa błędów.
Podczas gdy kod zaczyna zapełniać drugi bufor, a pierwszy jest zapisywany w osobnym wątku, ale z jakiegoś powodu zapis kończy się niepowodzeniem, główny wątek powinien być świadomy tego niepowodzenia.
Załóżmy, że interfejs CSequentialStreamWriter ma funkcję Write, która zwraca wartość bool lub zgłasza wyjątek, a więc mając błąd w oddzielnym wątku, musisz zapamiętać ten stan, więc przy następnym wywołaniu Write lub Finilize w głównym wątku metoda zwróci Fałsz lub spowoduje wyjątek. I tak naprawdę nie ma znaczenia, w którym momencie przestałeś zapełniać bufor, nawet jeśli po awarii zapisałeś jakieś dane - najprawdopodobniej plik byłby uszkodzony i bezużyteczny.
źródło
Proponuję spróbować mapowania plików . Używałem
mmap
w przeszłości w środowisku UNIX i byłem pod wrażeniem wysokiej wydajności, jaką mogłem osiągnąćźródło
Czy możesz
FILE*
zamiast tego użyć i zmierzyć osiągnięte wyniki?fwrite/write
Zamiastfstream
: należy użyć kilku opcji :Jeśli zdecydujesz się użyć
write
, spróbuj czegoś podobnego:Radziłbym również zajrzeć
memory map
. To może być twoja odpowiedź. Kiedyś musiałem przetworzyć plik 20 GB w innym, aby zapisać go w bazie danych, a plik nawet się nie otwiera. Tak więc rozwiązaniem jest wykorzystanie mapy pamięci. Zrobiłem toPython
jednak.źródło
FILE*
odpowiednik oryginalnego kodu używającego tego samego bufora 512 MB uzyskuje pełną prędkość. Twój obecny bufor jest za mały.2
odpowiada standardowemu błędowi, ale nadal zaleca się stosowanieSTDERR_FILENO
zamiast niego2
. Inną ważną kwestią jest to, że jednym z możliwych błędów, których nie można uzyskać, jest EINTR, ponieważ gdy otrzymasz sygnał przerwania, nie jest to prawdziwy błąd i powinieneś po prostu spróbować ponownie.Spróbuj użyć wywołań API open () / write () / close () i eksperymentuj z wielkością bufora wyjściowego. Mam na myśli: nie przepuszczaj całego bufora „wiele-wiele-bajtów” naraz, wykonaj kilka zapisów (tj. TotalNumBytes / OutBufferSize). OutBufferSize może mieć od 4096 bajtów do megabajta.
Kolejna próba - użyj WinAPI OpenFile / CreateFile i skorzystaj z tego artykułu MSDN, aby wyłączyć buforowanie (FILE_FLAG_NO_BUFFERING). I ten artykuł MSDN na temat WriteFile () pokazuje, jak uzyskać rozmiar bloku, aby dysk mógł poznać optymalny rozmiar bufora.
W każdym razie std :: ofstream jest opakowaniem i może występować blokowanie operacji we / wy. Pamiętaj, że przejście całej macierzy N-gigabajtów również zajmuje trochę czasu. Kiedy piszesz mały bufor, dostaje się do pamięci podręcznej i działa szybciej.
źródło
fstream
same w sobie nie są wolniejsze niż strumienie C, ale zużywają więcej procesora (szczególnie jeśli buforowanie nie jest odpowiednio skonfigurowane). Gdy procesor się nasyca, ogranicza szybkość operacji we / wy.Przynajmniej implementacja MSVC 2015 kopiuje 1 znak na raz do bufora wyjściowego, gdy bufor strumienia nie jest ustawiony (patrz
streambuf::xsputn
). Upewnij się więc, że ustawiłeś bufor strumienia (> 0) .Za
fstream
pomocą tego kodu mogę uzyskać prędkość zapisu 1500 MB / s (pełna prędkość mojego dysku M.2 SSD) :Wypróbowałem ten kod na innych platformach (Ubuntu, FreeBSD) i zauważyłem brak różnic we / wy, ale różnicę wykorzystania procesora wynoszącą około 8: 1 (
fstream
używane 8 razy więcej procesora ). Można więc sobie wyobrazić, że gdybym miał szybszy dysk,fstream
zapis byłby wolniejszy niżstdio
wersja.źródło
Spróbuj użyć plików zmapowanych w pamięci.
źródło
ReadFile
i takie dla dostępu sekwencyjnego, chociaż dla dostępu losowego mogą być lepsze.Jeśli skopiujesz coś z dysku A na dysk B w Eksploratorze, Windows zastosuje DMA. Oznacza to, że w przypadku większości procesów kopiowania procesor zasadniczo nie robi nic innego, jak powiedzieć kontrolerowi dysku, gdzie umieścić i uzyskać dane, eliminując cały krok w łańcuchu i taki, który wcale nie jest zoptymalizowany do przenoszenia dużych ilości danych - i mam na myśli sprzęt.
To, co robisz, wiąże się bardzo z procesorem. Chcę wskazać Ci część „Niektóre obliczenia dotyczące wypełnienia []”. Co uważam za niezbędne. Generujesz [], następnie kopiujesz z [] do bufora wyjściowego (to właśnie robi fstream :: write), a następnie generujesz ponownie itp.
Co robić? Wielowątkowość! (Mam nadzieję, że masz procesor wielordzeniowy)
źródło
Jeśli chcesz szybko pisać do strumieni plików, możesz zwiększyć strumień bufora odczytu:
Ponadto, podczas zapisywania dużej ilości danych do plików, czasem logicznie jest zwiększyć rozmiar pliku zamiast fizycznie, dzieje się tak, ponieważ podczas logicznego rozszerzania pliku system plików nie zeruje nowego miejsca przed zapisaniem do niego. Rozsądnie jest też logicznie rozszerzyć plik bardziej, niż jest to potrzebne, aby zapobiec dużej ilości plików. Plik logiczny rozbudowa jest obsługiwane w systemie Windows poprzez wywołanie
SetFileValidData
lubxfsctl
zXFS_IOC_RESVSP64
systemów XFS.źródło
kompiluję mój program w gcc w GNU / Linux i mingw w Win 7 i Win XP i działałem dobrze
możesz użyć mojego programu i aby utworzyć plik o wielkości 80 GB, po prostu zmień wiersz 33 na
po wyjściu z programu plik zostanie zniszczony, a następnie sprawdź plik, gdy jest uruchomiony
mieć program, który chcesz, po prostu zmień program
pierwszy to program Windows, a drugi to GNU / Linux
http://mustafajf.persiangig.com/Projects/File/WinFile.cpp
http://mustafajf.persiangig.com/Projects/File/File.cpp
źródło