Я ищу хороший способ скопировать файл (двоичный или текстовый). Я написал несколько образцов, все работают. Но я хочу услышать мнение опытных программистов.
Я не вижу хороших примеров и ищу способ, который работает с С++.
ANSI-C ПУТЬ
#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 использует это в "языке программирования C", более низкоуровневом)
#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-С++ - 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-АЛГОРИТМ-С++ - ПУТЬ
#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;
}
СОБСТВЕННОЙ-BUFFER-С++ - ПУТЬ
#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//требуется kernel >= 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;
}
Среда
- GNU/LINUX (Archlinux)
- Ядро 3.3
- GLIBC-2.15, LIBSTDС++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
- Использование RUNLEVEL 3 (многопользовательский, сетевой, терминальный, без GUI)
- INTEL SSD-Postville 80 ГБ, заполненный до 50%
- Скопируйте OGG-VIDEO-FILE объемом 270 МБ.
Шаги по воспроизведению
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
Результаты (используется время процессора)
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
Размер файла не изменяется.
sha256sum печатает те же результаты.
Видеофайл по-прежнему воспроизводится.
Вопросы
- Какой метод вы предпочитаете?
- Вы знаете лучшие решения?
- Вы видите ошибки в моем коде?
-
Знаете ли вы причину избежать решения?
-
FSTREAM (KISS, Streambuffer)
Мне очень нравится этот, потому что он очень короткий и простой. Насколько я знаю, оператор < перегружен для rdbuf() и ничего не конвертирует. Правильно?
Спасибо
Обновление 1
Таким образом, я изменил исходный код во всех образцах, чтобы открыть и закрыть дескрипторы файла включить в измерение clock(). Их нет никаких других существенных изменений в исходном коде. Результаты не изменились! Я также использовал время для проверки моих результатов.
Обновление 2
ANSI C sample changed: условие while-loop больше не вызывает feof(), вместо этого я переместил fread() в условие. Похоже, код работает на 10 000 часов быстрее.
Изменилось изменение: первые результаты были всегда буферизованы, потому что я повторил старую командную строку rm to.ogv && sync && время. /program для каждой программы несколько раз. Теперь я перезагружаю систему для каждой программы. Небуферизованные результаты новы и не удивляют. Небуферизованные результаты практически не изменились.
Если я не удаляю старую копию, программы реагируют друг на друга. Замена существующего файла с буферизацией происходит быстрее с помощью POSIX и SENDFILE, все остальные программы работают медленнее. Возможно, усечение или создание опций влияют на это поведение. Но переписывание существующих файлов с одинаковой копией не является реальным случаем использования.
Выполнение копии с помощью cp занимает 0,44 секунды без буферизации и 0,30 секунд. Таким образом, cp немного медленнее, чем образец POSIX. Выглядит хорошо для меня.
Возможно, я добавлю также образцы и результаты mmap() и copy_file()
из boost:: filesystem.
Обновление 3
Я также разместил это на странице блога и немного расширил его. Включает функцию splice(), которая является низкоуровневой функцией из ядра Linux. Возможно, последуют образцы с Java.
http://www.ttyhoney.com/blog/?page_id=69