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
#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
fstream
zdecydowanie jest dobrą opcją dla operacji na plikach.