web-dev-qa-db-ja.com

安全で効率的な方法でファイルをコピーする

私はファイル(バイナリまたはテキスト)をコピーするための良い方法を探しています。私はいくつかのサンプルを書きました、みんなうまくいきます。しかし、私は熟練したプログラマーの意見を聞きたいのです。

私は良い例が足りず、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プログラミング言語"でこれを使っています、もっと低レベルの)

#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++ - ストリームバッファ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++ - ウェイ

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

OWN-BUFFER-C++ - ウェイ

#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 80 GB、最大50%まで
  • 270 MBのOGGビデオファイルをコピーする

再現手順

 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使用時間)

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()の測定に含まれるようになりました。それらは、ソースコードに他の大きな変更はありません。結果は変わりません!また、timeを使って結果を再確認しました。

アップデート2
ANSI Cのサンプルが変更されました:while-loopの条件がもう呼び出されませんfeof()代わりにfread()を条件に入れました。コードの実行速度は10,000クロック速くなりました。

測定値の変更:以前の結果は常にバッファリングされていました。これは、各プログラムに対して古いコマンドラインrm to.ogv && sync && time ./programを数回繰り返したためです。今、私はすべてのプログラムのためにシステムを再起動します。バッファなしの結果は新しいものであり、驚くことではありません。バッファなしの結果はそれほど変わりませんでした。

古いコピーを削除しないと、プログラムの反応が異なります。既存のファイルの上書きbufferedはPOSIXとSENDFILEの方が速く、他のプログラムはすべて遅くなります。truncateまたはcreateのオプションがこの動作に影響を与える可能性があります。しかし、同じコピーで既存のファイルを上書きすることは現実世界のユースケースではありません。

cpでコピーを実行すると、バッファなしで0.44秒、バッファ付きで0.30秒かかります。だからcpはPOSIXサンプルより少し遅いです。私にはよく見えます。

Boost :: filesystemからmmap()copy_file()のサンプルと結果も追加するかもしれません。

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

284
Peter

正しい方法でファイルをコピーします。

#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);
232
Martin York

C++ 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を使用した2番目の形式と同等です( copy_file も参照)。

filesystemライブラリはもともとboost.filesystemとして開発され、ついにC++ 17以降のISO C++に統合されました。

48
manlio

多すぎる!

FILEname__はすでにバッファされているので、 "ANSI C"ウェイバッファは冗長です。 (この内部バッファのサイズはBUFSIZname__が実際に定義しているものです。)

"OWN-BUFFER-C++ - WAY"はfstreamname__を通過するときは遅くなります。これは多くの仮想ディスパッチを行い、再び内部バッファーまたは各ストリームオブジェクトを維持します。 ( "COPY-ALGORITHM-C++ - WAY"は、streambuf_iteratorクラスがストリーム層を迂回するのでこれを受けません。)

私は "COPY-ALGORITHM-C++ - WAY"を好むが、fstreamname__を構築せずに、実際のフォーマットが不要な場合は裸のstd::filebufインスタンスを作成するだけでよい。

生のパフォーマンスのためには、POSIXファイルディスクリプタに勝ることはできません。それは醜いですが移植性があり、どのプラットフォームでも高速です。

Linuxのやり方は信じられないほど速いようです - おそらくOSは関数がI/Oが終了する前に復帰させますか?いずれにせよ、それは多くのアプリケーションにとって十分な移植性がありません。

EDIT:「ネイティブLinux」は、読み書きを非同期I/Oでインターリーブすることでパフォーマンスを向上させることができます。コマンドを積み重ねると、ディスクドライバはいつシークするのが最善かを判断するのに役立ちます。あなたはBoost Asioやpthreadsを比較のために試すかもしれません。 「POSIXファイルディスクリプタに勝ることはできません」というのは…盲目的にコピーするのではなく、データを使って何かをしているのであればそれは事実です。

19
Potatoswatter

veryという重要な注意をしておきたいのですが、sendfile()を使用したLINUXメソッドには、2GBを超えるサイズのファイルをコピーできないという大きな問題があります。私はこの質問に従って実装していましたが、数GBのHDF5ファイルをコピーするのに使用していたため、問題が発生していました。

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

sendfile()は最大0x7ffff000(2,147,479,552)バイトを転送し、実際に転送されたバイト数を返します。 (これは、32ビットシステムと64ビットシステムの両方に当てはまります。)

14
rveale

Qtにはファイルをコピーする方法があります。

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

これを使用するには、 Qt (手順 here )をインストールしてプロジェクトに含める必要があります(以下の場合)。あなたはWindowsを使っていて、あなたは管理者ではありません。代わりにQt here をダウンロードできます。 この答え もご覧ください。

2
Donald Duck

ブーストが好きな人のために:

boost::filesystem::path mySourcePath("foo.bar");
boost::filesystem::path myTargetPath("bar.foo");

// Variant 1: Overwrite existing
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::overwrite_if_exists);

// Variant 2: Fail if exists
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::fail_if_exists);

boost :: filesystem :: pathwpathとしても利用可能です。 Unicodeの場合そしてあなたも使うことができる

using namespace boost::filesystem

あなたがそれらの長い型名を好きではないならば

1
anhoppe