1. ホーム
  2. c++

[解決済み】正気で、安全で、効率的な方法でファイルをコピーする。

2022-03-27 20:19:05

質問

ファイル(バイナリまたはテキスト)をコピーする良い方法を探しています。私はいくつかのサンプルを書き、誰もが動作します。しかし、私はベテランのプログラマーの意見を聞きたいです。

良い例がなく、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では、より低レベルのC言語(" The C programming language")でこれを使用します)

#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;
}

コピーアルゴリズム-c++-方法

#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;
}

自バッファ-c++-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);

    // 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 // カーネル >= 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、LIBSTDC++ 4.7 (GCC-LIBS)、GCC 4.7、Coreutils 8.16
  • RUNLEVEL 3を使用(マルチユーザー、ネットワーク、ターミナル、GUIなし)
  • INTEL SSD-Postville 80GB、50%まで充填。
  • 270MBのOGG-VIDEO-FILEをコピーする。

再現のための手順

 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

結果(使用CPU TIME)

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

の測定にファイルディスクリプタの開閉が含まれるように、全サンプルでソースを変更しました。 クロック() . その他、ソースコードに大きな変更はありません。結果も変わりません。また 時間 でダブルチェック。

アップデート2

ANSI Cのサンプルが変更されました。 whileループ はもう呼び出されません。 feof() その代わりに fread() を条件に入れています。見た目には、コードの実行が1万クロック速くなったように見えます。

測定値の変更:以前の結果は常にバッファリングされていました。 rm to.ogv && sync && time ./program を各番組ごとに数回繰り返す。今は、プログラムごとにシステムを再起動しています。バッファリングされていない結果は新しいもので、何の驚きも示していません。バッファリングされていない結果は本当に変わりませんでした。

古いコピーを削除しない場合、プログラムの反応は異なります。既存のファイルを上書きする バッファリング はPOSIXとSENDFILEで速く、他のすべてのプログラムは遅くなります。もしかしたら、オプションの 切り捨てる または 作成 は、この動作に影響を与えます。しかし、既存のファイルを同じコピーで上書きすることは、現実的なユースケースとは言えません。

を使用してコピーを実行します。 シーピー はアンバッファリングで0.44秒、バッファリングで0.30秒かかります。そのため シーピー はPOSIXのサンプルより少し遅いです。私には問題なさそうです。

のサンプルと結果も追加しようかな。 mmap() copy_file() をboost::filesystemから取得しました。

アップデート3

これもブログページに載せて少し拡張しました。含まれるもの splice() これはLinuxカーネルの低レベル関数です。多分、Javaを使ったサンプルはもっと続くでしょう。 http://www.ttyhoney.com/blog/?page_id=69

解決方法は?

ファイルをまともにコピーする。

#include <fstream>

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

    dst << src.rdbuf();
}

これはとてもシンプルで直感的に読めるので、追加コストをかける価値があります。もし、それを何度も行うのであれば、OSのファイルシステムへの呼び出しにフォールバックするのがベターでしょう。きっと boost は、そのファイルシステム・クラスにコピー・ファイル・メソッドを持っています。

ファイルシステムと対話するためのCメソッドがある。

#include <copyfile.h>

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