Скопируйте файл в эффективном, безопасном и эффективном виде

Я ищу хороший способ скопировать файл (двоичный или текстовый). Я написал несколько образцов, все работают. Но я хочу услышать мнение опытных программистов.

Я не вижу хороших примеров и ищу способ, который работает с С++.

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

Ответ 1

Скопируйте файл разумным способом:

int main()
{
    std::ifstream  src("from.ogv", std::ios::binary);
    std::ofstream  dst("to.ogv",   std::ios::binary);

    dst << src.rdbuf();
}

Это так просто и интуитивно понятно, что стоит дополнительных затрат. Если бы мы делали это много, лучше отказаться от вызовов ОС в файловую систему. Я уверен, что boost имеет метод файла копии в своем классе файловой системы.

Существует способ C для взаимодействия с файловой системой:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

Ответ 2

С С++ 17 стандартный способ копирования файла будет включать заголовок <filesystem> и используя:

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to);

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to,
                std::filesystem::copy_options options);

Первая форма эквивалентна второй с copy_options::none, используемой в качестве опций (см. также copy_file).

Библиотека filesystem была первоначально разработана как boost.filesystem и, наконец, объединена с ISO С++ с С++ 17.

Ответ 3

Слишком много!

Путь "ANSI C" избыточен, поскольку FILE уже буферизирован. (Размер этого внутреннего буфера - это то, что фактически определяет BUFSIZ.)

"OWN-BUFFER-С++ - WAY" будет медленным, поскольку он проходит через fstream, который выполняет много виртуальной диспетчеризации и снова поддерживает внутренние буферы или каждый объект потока. ( "COPY-ALGORITHM-С++-WAY" этого не терпит, так как класс streambuf_iterator обходит слой потока.)

Я предпочитаю "COPY-ALGORITHM-С++-WAY", но без построения fstream просто создавайте оголенные экземпляры std::filebuf, когда не требуется никакого форматирования.

Для сырой производительности вы не можете бить дескрипторы файлов POSIX. Это уродливое, но портативное и быстрое на любой платформе.

Путь Linux выглядит невероятно быстрым - возможно, операционная система вернула функцию до завершения ввода-вывода? В любом случае, это не достаточно переносимо для многих приложений.

EDIT: Ах, "родной Linux" может улучшить производительность, чередуя чтения и записи с асинхронным вводом-выводом. Предоставление команд наложения может помочь драйверу диска решить, когда лучше всего искать. Вы можете попробовать Boost Asio или pthreads для сравнения. Что касается "не может бить дескрипторы файлов POSIX"... хорошо, что это правда, если вы делаете что-либо с данными, а не только слепо копируете.

Ответ 4

Я хочу сделать очень важное замечание о том, что метод LINUX, использующий sendfile(), имеет серьезную проблему в том, что он не может копировать файлы размером более 2 ГБ! Я реализовал его после этого вопроса и столкнулся с проблемами, потому что использовал его для копирования файлов HDF5, размер которых был большим.

http://man7.org/linux/man-pages/man2/sendfile.2.html

sendfile() будет передавать не более 0x7ffff000 (2,147,479,552) байт, возвращая количество фактически переданных байтов. (Это верно для как 32-битные, так и 64-битные системы.)

Ответ 5

Qt имеет способ копирования файлов:

#include <QFile>
QFile::copy("originalFile.example","copiedFile.example");

Обратите внимание, что для использования этого параметра вы должны установить Qt (инструкции здесь) и включите его в свой проект (если вы используете Windows и вы не являетесь администратором, вы можете скачать Qt здесь вместо этого). Также см. этот ответ.