Poszukuję dobrego sposobu na skopiowanie pliku (binarnego lub tekstowego). Napisałem kilka próbek, każdy działa. Ale chcę usłyszeć opinię doświadczonych programistów.
Brakuje dobrych przykładów i szukam sposobu, który działa z C ++.
ANSI-C-WAY
#include <iostream>
#include <cstdio> // fopen, fclose, fread, fwrite, BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE default is 8192 bytes
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
FILE* source = fopen("from.ogv", "rb");
FILE* dest = fopen("to.ogv", "wb");
// clean and more secure
// feof(FILE* stream) returns non-zero if the end of file indicator for stream is set
while (size = fread(buf, 1, BUFSIZ, source)) {
fwrite(buf, 1, size, dest);
}
fclose(source);
fclose(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
POSIX-WAY (K&R używa tego w „języku programowania C”, więcej niższego poziomu)
#include <iostream>
#include <fcntl.h> // open
#include <unistd.h> // read, write, close
#include <cstdio> // BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE defaults to 8192
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
while ((size = read(source, buf, BUFSIZ)) > 0) {
write(dest, buf, size);
}
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
KISS-C ++ - Streambuffer-WAY
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
dest << source.rdbuf();
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
COPY-ALGORITHM-C ++ - DROGA
#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
istreambuf_iterator<char> begin_source(source);
istreambuf_iterator<char> end_source;
ostreambuf_iterator<char> begin_dest(dest);
copy(begin_source, end_source, begin_dest);
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
WŁASNY BUFOR-C ++ - SPOSÓB
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
// file size
source.seekg(0, ios::end);
ifstream::pos_type size = source.tellg();
source.seekg(0);
// allocate memory for buffer
char* buffer = new char[size];
// copy file
source.read(buffer, size);
dest.write(buffer, size);
// clean up
delete[] buffer;
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
LINUX-WAY // wymaga jądra> = 2.6.33
#include <iostream>
#include <sys/sendfile.h> // sendfile
#include <fcntl.h> // open
#include <unistd.h> // close
#include <sys/stat.h> // fstat
#include <sys/types.h> // fstat
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
// struct required, rationale: function stat() exists also
struct stat stat_source;
fstat(source, &stat_source);
sendfile(dest, source, 0, stat_source.st_size);
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
Środowisko
- GNU / LINUX (Archlinux)
- Jądro 3.3
- GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
- Korzystanie z RUNLEVEL 3 (Multiuser, Network, Terminal, bez GUI)
- INTEL SSD-Postville 80 GB, wypełniony do 50%
- Skopiuj 270 MB OGG-VIDEO-FILE
kroki ku reprodukcji
1. $ rm from.ogg
2. $ reboot # kernel and filesystem buffers are in regular
3. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
4. $ sha256sum *.ogv # checksum
5. $ rm to.ogg # remove copy, but no sync, kernel and fileystem buffers are used
6. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
Wyniki (wykorzystany CZAS PROCESORA)
Program Description UNBUFFERED|BUFFERED
ANSI C (fread/frwite) 490,000|260,000
POSIX (K&R, read/write) 450,000|230,000
FSTREAM (KISS, Streambuffer) 500,000|270,000
FSTREAM (Algorithm, copy) 500,000|270,000
FSTREAM (OWN-BUFFER) 500,000|340,000
SENDFILE (native LINUX, sendfile) 410,000|200,000
Rozmiar pliku się nie zmienia.
sha256sum drukuje te same wyniki.
Plik wideo jest nadal odtwarzany.
pytania
- Jaką metodę wolisz?
- Czy znasz lepsze rozwiązania?
- Czy widzisz jakieś błędy w moim kodzie?
Czy znasz powód, dla którego chcesz uniknąć rozwiązania?
FSTREAM (KISS, Streambuffer)
Naprawdę podoba mi się ten, ponieważ jest naprawdę krótki i prosty. O ile wiem, operator << jest przeciążony dla rdbuf () i niczego nie konwertuje. Poprawny?
Dzięki
Aktualizacja 1
Zmieniłem źródło we wszystkich próbkach w taki sposób, że otwieranie i zamykanie deskryptorów plików jest uwzględniane w pomiarze clock () . Nie są to żadne inne znaczące zmiany w kodzie źródłowym. Wyniki się nie zmieniły! Poświęciłem też czas na dokładne sprawdzenie moich wyników.
Aktualizacja 2
Próbka ANSI C zmieniona: Stan pętli while nie wywołuje już feof (), zamiast tego przeniosłem fread () do tego warunku. Wygląda na to, że kod działa teraz o 10 000 zegarów szybciej.
Zmieniono pomiar: poprzednie wyniki były zawsze buforowane, ponieważ kilkakrotnie powtórzyłem stary wiersz poleceń rm to.ogv && sync && time ./program dla każdego programu. Teraz ponownie uruchamiam system dla każdego programu. Niebuforowane wyniki są nowe i nie zaskakują. Niebuforowane wyniki tak naprawdę się nie zmieniły.
Jeśli nie usunę starej kopii, programy zareagują inaczej. Zastępowanie istniejącego pliku buforowanego jest szybsze w POSIX i SENDFILE, wszystkie inne programy działają wolniej. Może opcje obcinania lub tworzenia mają wpływ na to zachowanie. Jednak nadpisywanie istniejących plików tą samą kopią nie jest prawdziwym przypadkiem użycia.
Wykonanie kopii za pomocą cp zajmuje 0,44 sekundy bez buforowania i 0,30 sekundy buforowane. Tak więc proces cp jest nieco wolniejszy niż próbka POSIX. Wygląda dobrze dla mnie.
Może dodam także próbki i wyniki mmap () oraz copy_file()
z boost :: fileystem.
Aktualizacja 3
Umieściłem to również na stronie blogu i nieco ją rozszerzyłem. W tym splice () , która jest funkcją niskiego poziomu z jądra Linux. Być może pojawi się więcej próbek z Javą.
http://www.ttyhoney.com/blog/?page_id=69
fstream
zdecydowanie jest dobrą opcją dla operacji na plikach.#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Odpowiedzi:
Skopiuj plik w rozsądny sposób:
Czytanie jest tak proste i intuicyjne, że jest warte dodatkowych kosztów. Jeśli często to robimy, lepiej polegać na wywołaniach systemu operacyjnego do systemu plików. Jestem pewien, że
boost
ma metodę kopiowania plików w swojej klasie systemu plików.Istnieje metoda C do interakcji z systemem plików:
źródło
copyfile
nie jest przenośny; Myślę, że jest specyficzny dla Mac OS X. Z pewnością nie istnieje w systemie Linux.boost::filesystem::copy_file
jest prawdopodobnie najbardziej przenośnym sposobem kopiowania pliku za pośrednictwem macierzystego systemu plików.W C ++ 17 standardowym sposobem kopiowania pliku będzie dołączenie
<filesystem>
nagłówka i użycie:Pierwsza forma jest równoważna drugiej z
copy_options::none
zastosowanymi jako opcje (patrz takżecopy_file
).filesystem
Biblioteki został pierwotnie opracowany jakoboost.filesystem
i wreszcie połączyła ISO C ++ jako C ++ 17.źródło
bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);
?Za dużo!
Bufor „ANSI C” jest redundantny, ponieważ a
FILE
jest już buforowany. (Rozmiar tego bufora wewnętrznego jest tym, coBUFSIZ
faktycznie definiuje.)„OWN-BUFFER-C ++ - WAY” będzie powolny z upływem czasu
fstream
, co powoduje wiele wirtualnych wysyłek i ponownie utrzymuje wewnętrzne bufory lub każdy obiekt strumienia. („COPY-ALGORITHM-C ++ - WAY” tego nie cierpi, ponieważstreambuf_iterator
klasa omija warstwę strumienia.)Wolę „COPY-ALGORITHM-C ++ - WAY”, ale bez konstruowania
fstream
, po prostu twórz nagiestd::filebuf
instancje, gdy nie jest potrzebne żadne formatowanie.Aby uzyskać surową wydajność, nie można pokonać deskryptorów plików POSIX. Jest brzydki, ale przenośny i szybki na każdej platformie.
Sposób na Linuksa wydaje się być niesamowicie szybki - być może system operacyjny pozwala na powrót funkcji przed zakończeniem operacji we / wy? W każdym razie nie jest to wystarczająco przenośne dla wielu aplikacji.
EDYCJA : Ach, „macierzysty Linux” może poprawiać wydajność poprzez przeplatanie odczytów i zapisów za pomocą asynchronicznych operacji we / wy. Uporządkowanie poleceń może pomóc sterownikowi dysku zdecydować, kiedy najlepiej szukać. Możesz spróbować Boost Asio lub pthreads do porównania. Jeśli chodzi o „nie można pokonać deskryptorów plików POSIX”… cóż, to prawda, jeśli robisz coś z danymi, a nie tylko ślepo kopiujesz.
źródło
sendfile()
kopiuje dane między jednym deskryptorem pliku a drugim. Ponieważ kopiowanie odbywa się w jądrze,sendfile()
jest bardziej wydajne niż połączenieread(2)
iwrite(2)
, co wymagałoby transferu danych do iz przestrzeni użytkownika.”: kernel.org/doc/man-pages /online/pages/man2/sendfile.2.htmlfilebuf
obiektów?Chcę bardzo ważną uwagę, że metoda LINUX za pomocą sendfile () ma poważny problem, ponieważ nie może kopiować plików o rozmiarze większym niż 2 GB! Zaimplementowałem go po tym pytaniu i napotykałem problemy, ponieważ użyłem go do kopiowania plików HDF5 o rozmiarze wielu GB.
http://man7.org/linux/man-pages/man2/sendfile.2.html
źródło
off64_t
pozwala na użycie pętli do kopiowania dużych plików, jak pokazano w odpowiedzi na połączone pytanie.Qt ma metodę kopiowania plików:
Pamiętaj, że aby go użyć, musisz zainstalować Qt (instrukcje tutaj ) i dołączyć go do swojego projektu (jeśli używasz systemu Windows i nie jesteś administratorem, możesz pobrać tutaj Qt ). Zobacz także tę odpowiedź .
źródło
QFile::copy
jest absurdalnie wolny z powodu buforowania 4k .Qt
. Używam,5.9.2
a prędkość jest na równi z natywną implementacją. Btw. patrząc na kod źródłowy, Qt wydaje się faktycznie wywoływać implementację natywną.Dla tych, którzy lubią boost:
Zauważ, że boost :: fileystem :: path jest również dostępny jako wpath dla Unicode. I że możesz również użyć
jeśli nie podobają ci się te długie nazwy
źródło
Nie jestem do końca pewien, jaki jest „dobry sposób” kopiowania pliku, ale zakładając, że „dobry” oznacza „szybki”, mógłbym nieco poszerzyć temat.
Obecne systemy operacyjne od dawna są zoptymalizowane pod kątem uruchamiania kopii pliku młyna. Żaden sprytny kawałek kodu nie pokona tego. Możliwe, że niektóre warianty technik kopiowania okażą się szybsze w niektórych scenariuszach testowych, ale najprawdopodobniej byłyby gorsze w innych przypadkach.
Zazwyczaj
sendfile
funkcja prawdopodobnie powraca przed zatwierdzeniem zapisu, co sprawia wrażenie, że jest szybsza niż reszta. Nie przeczytałem kodu, ale z pewnością dzieje się tak dlatego, że przydziela on własny dedykowany bufor, wymieniając pamięć na czas. I powód, dla którego nie będzie działać dla plików większych niż 2 Gb.Tak długo, jak masz do czynienia z niewielką liczbą plików, wszystko dzieje się w różnych buforach (środowisko uruchomieniowe C ++ jest pierwsze, jeśli używasz
iostream
, wewnętrzne systemy operacyjne, najwyraźniej dodatkowy bufor wielkości pliku w przypadkusendfile
). Rzeczywisty nośnik pamięci jest dostępny dopiero po przeniesieniu wystarczającej ilości danych, aby było warte kłopotu z zakręceniem dysku twardego.Podejrzewam, że w niektórych przypadkach można nieco poprawić wydajność. Z czubka mojej głowy:
copy_file
sekwencyjnie (chociaż nie zauważysz różnicy, dopóki plik mieści się w pamięci podręcznej systemu operacyjnego)Ale wszystko to jest poza zakresem funkcji kopiowania plików ogólnego przeznaczenia.
Tak więc, moim zdaniem przyprawionym programistą, kopia pliku C ++ powinna po prostu korzystać z
file_copy
dedykowanej funkcji C ++ 17 , chyba że wiadomo więcej na temat kontekstu, w którym występuje kopia pliku i można opracować pewne sprytne strategie przechytrzenia systemu operacyjnego.źródło