326

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

良い例がなく、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++-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++-WAY

#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 ++-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 80 GB、最大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時間)

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
のANSICサンプルが変更されました:whileループの条件がfeof()を呼び出さなくなり、代わりにfread()を条件に移動しました。コードは10,000クロック速く実行されるように見えます。

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

古いコピーを削除しないと、プログラムの反応が異なります。バッファリングされた既存のファイルの上書きは、POSIXとSENDFILEを使用すると高速になり、他のすべてのプログラムは低速になります。おそらく、オプションを切り捨てたり作成したりすると、この動作に影響があります。ただし、既存のファイルを同じコピーで上書きすることは、実際のユースケースではありません。

cpを使用してコピーを実行するには、バッファなしで0.44秒、バッファ付きで0.30秒かかります。したがって、 cpはPOSIXサンプルよりも少し遅いです。私には元気そうです。

たぶん、 mmap()copy_file()boost::filesystemからのサンプルと結果も追加します。

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

4

7 に答える 7

281

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

#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);
于 2012-04-17T16:49:38.940 に答える
71

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.filesystemC++17として開発され、最終的にISOC++にマージされました。

于 2016-07-27T15:00:09.740 に答える
21

多すぎる!

「ANSIC」ウェイバッファは、aFILEがすでにバッファリングされているため、冗長です。(この内部バッファーのサイズは、BUFSIZ実際に定義されているものです。)

fstream「OWN-BUFFER-C++-WAY」は、多くの仮想ディスパッチを実行し、内部バッファまたは各ストリームオブジェクトを維持するため、処理が遅くなります。(「COPY-ALGORITHM-C ++-WAY」は、streambuf_iteratorクラスがストリームレイヤーをバイパスするため、これに影響されません。)

私は「COPY-ALGORITHM-C++-WAY」が好きですが、実際のフォーマットが必要ない場合は、を作成せずfstreamにベアインスタンスを作成するだけです。std::filebuf

生のパフォーマンスのために、POSIXファイル記述子に勝るものはありません。それは醜いですが、どのプラットフォームでもポータブルで高速です。

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

編集:ああ、「ネイティブLinux」は、非同期I / Oで読み取りと書き込みをインターリーブすることにより、パフォーマンスを向上させている可能性があります。コマンドを積み上げると、ディスクドライバが最適なタイミングを決定するのに役立ちます。比較のためにBoostAsioまたはpthreadsを試してみてください。「POSIXファイル記述子に勝るものはありません」については…まあ、それは、盲目的にコピーするだけでなく、データを使って何かをしている場合にも当てはまります。

于 2012-04-17T16:52:09.113 に答える
17

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

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

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

于 2016-02-29T12:33:40.743 に答える
2

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

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

これを使用するには、Qtをインストールして(手順はこちら)、プロジェクトに含める必要があることに注意してください(Windowsを使用していて、管理者でない場合は、代わりにQtをここからダウンロードできます)。この回答も参照してください。

于 2016-10-23T12:43:41.813 に答える
2

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

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::pathはUnicodeのwpathとしても使用できることに注意してください。そして、あなたも使用できること

using namespace boost::filesystem

それらの長い型の名前が気に入らない場合

于 2018-11-29T15:19:36.647 に答える
1

ファイルをコピーする「良い方法」が何であるかはよくわかりませんが、「良い」が「速い」を意味すると仮定すると、主題を少し広げることができます。

現在のオペレーティングシステムは、ミルファイルコピーの実行を処理するために長い間最適化されてきました。巧妙なコードでこれに勝るものはありません。一部のテストシナリオでは、コピー手法の一部のバリエーションがより高速であることが証明される可能性がありますが、他の場合では、より悪くなる可能性があります。

通常、このsendfile関数は書き込みがコミットされる前に戻る可能性があるため、他の関数よりも高速であるという印象を与えます。私はコードを読んでいませんが、それは間違いなく、それが独自の専用バッファーを割り当て、時間とメモリを交換しているためです。そしてそれが2Gbより大きいファイルのために働かない理由。

少数のファイルを処理している限り、すべてがさまざまなバッファー内で発生します(C ++ランタイムを使用する場合は最初iostream、OS内部のもの、場合はファイルサイズの追加バッファーsendfile)。実際のストレージメディアにアクセスできるのは、ハードディスクを回転させる手間をかけるだけの十分なデータが移動された場合のみです。

特定のケースでは、パフォーマンスを少し改善できると思います。頭のてっぺんから:

  • 同じディスクに巨大なファイルをコピーする場合、OSよりも大きなバッファを使用すると、状況が少し改善される可能性があります(ただし、ここではおそらくギガバイトについて話します)。
  • 同じファイルを2つの異なる物理的な宛先にコピーする場合は、2つをcopy_file順番に呼び出すよりも、一度に3つのファイルを開く方が速いでしょう(ただし、ファイルがOSキャッシュに収まる限り、違いに気付くことはほとんどありません)。
  • HDDで多数の小さなファイルを処理している場合は、シーク時間を最小限に抑えるためにそれらをバッチで読み取ることをお勧めします(ただし、OSはすでにディレクトリエントリをキャッシュして、クレイジーで小さなファイルのようにシークしないようにすると、ディスク帯域幅が大幅に減少する可能性があります)。

しかし、それはすべて、汎用ファイルコピー機能の範囲外です。

file_copyしたがって、私のほぼ間違いなくベテランのプログラマーの意見では、C ++ファイルコピーは、ファイルコピーが発生するコンテキストについてより多くのことがわかっていて、OSを凌駕するための巧妙な戦略を考案できる場合を除いて、C++17専用関数を使用する必要があります。

于 2020-01-08T16:06:08.333 に答える