ファイル (バイナリまたはテキスト) をコピーする良い方法を探しています。いくつかのサンプルを書きましたが、どれもうまくいきます。しかし、経験豊富なプログラマーの意見を聞きたいです。
良い例がないので、C++ で動作する方法を探しています。
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 方式(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;
}
コピーアルゴリズム-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++ 方式
#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 (アーチリナックス)
- カーネル 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()
の測定に含まれるようにしました。ソース コードには他に大きな変更はありません。結果は変わりません。また、時間を使用して結果を再確認しました。
更新 2 ANSI C サンプルが変更されました: while ループ
の条件はfeof()を呼び出さなくなりました。代わりにfread()を条件に移動しました。コードの実行速度が 10,000 クロック速くなったようです。
測定が変更されました: 以前の結果は常にバッファリングされていました。これは、各プログラムに対して古いコマンド ラインrm to.ogv && sync && time ./programを数回繰り返したためです。現在は、プログラムごとにシステムを再起動しています。バッファリングされていない結果は新しく、驚くようなものではありません。バッファリングされていない結果は実際には変更されていません。
古いコピーを削除しないと、プログラムの反応が異なります。バッファリングされた既存のファイルを上書きすると、POSIX と SENDFILE を使用すると高速になりますが、他のすべてのプログラムは低速になります。おそらく、 truncateまたはcreateオプションがこの動作に影響しているのでしょう。ただし、同じコピーで既存のファイルを上書きすることは、実際の使用例ではありません。
cpでコピーを実行すると、バッファなしで 0.44 秒、バッファ付きで 0.30 秒かかります。つまり、cp はPOSIX サンプルよりも少し遅いです。私にとっては問題ないようです。
おそらく、 mmap()とcopy_file()
boost::filesystemのサンプルと結果も追加するでしょう。
アップデート 3
これをブログ ページにも掲載し、少し拡張しました。Linuxカーネルの低レベル関数であるsplice()も含まれています。Java を使用したサンプルがさらに追加される可能性があります。http://www.ttyhoney.com/blog/?page_id=69
ベストアンサー1
正しい方法でファイルをコピーします。
#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);