私はSSD(ソリッドステートドライブ)に大量のデータを書き込もうとしています。そして膨大な量で私は80GBを意味します。
私は解決のためにWebを閲覧しました、しかし私が思い付いた最高のものはこれでした:
#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
std::fstream myfile;
myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
//Here would be some error handling
for(int i = 0; i < 32; ++i){
//Some calculations to fill a[]
myfile.write((char*)&a,size*sizeof(unsigned long long));
}
myfile.close();
}
Visual Studio 2010と完全な最適化でコンパイルされ、Windows 7で実行されるこのプログラムは最大約20MB /秒です。私が本当に悩んでいるのは、Windowsが他のSSDからこのSSDにファイルを150MB/sから200MB/sの間のどこかでコピーできることです。だから少なくとも7倍速いです。だからこそ私はもっと早く行けるはずだと思います。
どのように私は私の執筆をスピードアップすることができますか?
これは仕事をしました:
#include <stdio.h>
const unsigned long long size = 8ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
FILE* pFile;
pFile = fopen("file.binary", "wb");
for (unsigned long long j = 0; j < 1024; ++j){
//Some calculations to fill a[]
fwrite(a, 1, size*sizeof(unsigned long long), pFile);
}
fclose(pFile);
return 0;
}
私はちょうど36秒で8GBを計った、それは約220MB/sであり、私はそれが私のSSDを最大にすると思う。このコードでは2〜5%しか使用していませんが、問題のコードでは1つのコアを100%使用していました。
どうもありがとうございました。
更新:5年が経過した。コンパイラ、ハードウェア、ライブラリ、そして私の要求は変わりました。だから私はコードにいくつかの変更を加え、いくつかの測定をしたのです。
最初のコード
#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>
std::vector<uint64_t> GenerateData(std::size_t bytes)
{
assert(bytes % sizeof(uint64_t) == 0);
std::vector<uint64_t> data(bytes / sizeof(uint64_t));
std::iota(data.begin(), data.end(), 0);
std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
return data;
}
long long option_1(std::size_t bytes)
{
std::vector<uint64_t> data = GenerateData(bytes);
auto startTime = std::chrono::high_resolution_clock::now();
auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
myfile.write((char*)&data[0], bytes);
myfile.close();
auto endTime = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}
long long option_2(std::size_t bytes)
{
std::vector<uint64_t> data = GenerateData(bytes);
auto startTime = std::chrono::high_resolution_clock::now();
FILE* file = fopen("file.binary", "wb");
fwrite(&data[0], 1, bytes, file);
fclose(file);
auto endTime = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}
long long option_3(std::size_t bytes)
{
std::vector<uint64_t> data = GenerateData(bytes);
std::ios_base::sync_with_stdio(false);
auto startTime = std::chrono::high_resolution_clock::now();
auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
myfile.write((char*)&data[0], bytes);
myfile.close();
auto endTime = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}
int main()
{
const std::size_t kB = 1024;
const std::size_t MB = 1024 * kB;
const std::size_t GB = 1024 * MB;
for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl;
for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl;
for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl;
return 0;
}
これで、コードはVisual Studio 2017とg ++ 7.2.0(これは私の要件の1つになりました)でコンパイルされます。コードを2つの設定で実行します。
以下の測定値が得られました(明らかな異常値であるため、1MBの値を切り捨てた後): option1とoption3の両方でSSDが最大になります。当時私のマシンではoption2が最速のコードだったので、これが見られるとは思っていませんでした。
TL; DR:私の測定値はFILE
よりstd::fstream
を使うことを示しています。
次のことを順番に試してください。
バッファサイズが小さい。一度に〜2 MiBを書くのは良いスタートかもしれません。私の最後のラップトップでは、〜512 KiBが最適な場所でしたが、私はまだSSDをテストしていません。
注:非常に大きなバッファーはパフォーマンスが低下する傾向があることに気付きました。私は以前に512 KiBのバッファの代わりに16 MiBのバッファを使うことで速度の低下に気づいた。
ファイルを開くには_open
(またはWindowsに適切な場合は_topen
)を使用してから_write
を使用します。これはおそらく多くのバッファリングを避けるでしょうが、それは確実ではありません。
CreateFile
やWriteFile
などのWindows固有の関数を使用する。それは標準ライブラリでバッファリングを避けるでしょう。
Std :: stream/FILE/deviceの間に違いはありません。バッファリングと非バッファリングの間.
また注意してください。
コードが63秒で実行されるのを見ています。
したがって、転送速度は次のようになります。260M/s(私のSSDはあなたのものよりわずかに速く見えます)。
64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/
= 16G
= 16G/63 = 260M/s
私はstd :: fstreamからFILE *に移行しても増加はありません。
#include <stdio.h>
using namespace std;
int main()
{
FILE* stream = fopen("binary", "w");
for(int loop=0;loop < 32;++loop)
{
fwrite(a, sizeof(unsigned long long), size, stream);
}
fclose(stream);
}
そのため、C++ストリームは、基盤となるライブラリが許容する速度で動作します。
しかし、私はそれをOS上に構築されたアプリケーションとOSを比較するのは不公平だと思います。アプリケーションは(ドライブがSSDであることを認識していない)仮定することはできませんので、転送のためにOSのファイルメカニズムを使用します。
OSが仮定をする必要はありませんが。関連するドライブの種類を認識し、データ転送に最適な手法を使用できます。この場合、直接メモリからメモリへの転送です。 80 Gをメモリ内の1つの場所から別の場所にコピーするプログラムを書いてみて、それがどれくらい速いかを確かめてください。
私は自分のコードを低レベルの呼び出しを使うように変更しました:
つまり、バッファリングはありません。
#include <fcntl.h>
#include <unistd.h>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
int data = open("test", O_WRONLY | O_CREAT, 0777);
for(int loop = 0; loop < 32; ++loop)
{
write(data, a, size * sizeof(unsigned long long));
}
close(data);
}
これは違いがなかった。
NOTE:普通のドライブをお持ちの場合、私のドライブはSSDドライブです。2つの方法に違いがあるかもしれません。上記。しかし、非バッファリングとバッファリング(バッファサイズを超える大きなチャンクを書き込むとき)を予想していても違いはありません。
C++でファイルをコピーする最速の方法を試したことがありますか
int main()
{
std::ifstream input("input");
std::ofstream output("ouptut");
output << input.rdbuf();
}
最善の解決策は、ダブルバッファリングによる非同期書き込みを実装することです。
タイムラインを見てください。
------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|
'F'はバッファを埋める時間を表し、 'W'はバッファをディスクに書き込む時間を表します。それで、バッファをファイルに書き込む間の時間を無駄にすることにおける問題。しかし、別のスレッドに書き込みを実装することで、次のバッファをすぐに埋め始めることができます。
------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
|WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|
F - 1次バッファーの充填
f - 2番目のバッファを埋める
W - ファイルへの最初のバッファの書き込み
w - ファイルへの2番目のバッファの書き込み
_ - 操作が完了するまで待ちます
バッファスワップを使用するこのアプローチは、バッファをいっぱいにすることがより複雑な計算を必要とするとき(したがって、より多くの時間)に非常に役立ちます。私はCSequentialStreamWriterクラスを常に実装して内部での非同期書き込みを隠しています。そのため、エンドユーザーにとっては、インターフェイスにはWrite関数しかありません。
また、バッファサイズはディスククラスタサイズの倍数でなければなりません。それ以外の場合は、隣接する2つのディスククラスタに単一のバッファを書き込むことでパフォーマンスが低下します。
最後のバッファを書きます。
最後にWrite関数を呼び出すときは、現在のバッファがいっぱいになっていることをディスクにも書き込む必要があることを確認する必要があります。したがってCSequentialStreamWriterには別のメソッドが必要です。たとえば、Finalize(final buffer flush)と言います。このメソッドは、データの最後の部分をディスクに書き込みます。
エラー処理。
コードが2番目のバッファを埋め始め、1番目のバッファが別のスレッドに書き込まれているが、何らかの理由で書き込みに失敗した場合、メインスレッドはその失敗を認識している必要があります。
------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|
CSequentialStreamWriterのインターフェースがWrite関数を返すか、例外をスローして別のスレッドでエラーが発生したとしましょう。その状態を覚えておく必要があるので、次にメインスレッドでWriteまたはFinilizeを呼び出すと、メソッドは戻ります。偽または例外をスローします。また、障害発生後にデータを書き込んだ場合でも、どの時点でバッファをいっぱいにしなくてもかまいません。ファイルが破損していて無駄になる可能性があります。
ファイルマッピング を試してみることをお勧めします。私は過去にUNIX環境でmmap
を使っていましたが、私が達成できた高性能に感銘を受けました
代わりにFILE*
を使って、あなたが得たパフォーマンスを測定することができますか? fstream
の代わりにfwrite/write
を使用するという方法があります。
#include <stdio.h>
int main ()
{
FILE * pFile;
char buffer[] = { 'x' , 'y' , 'z' };
pFile = fopen ( "myfile.bin" , "w+b" );
fwrite (buffer , 1 , sizeof(buffer) , pFile );
fclose (pFile);
return 0;
}
write
を使用することにした場合は、同様のことを試してください。
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int filedesc = open("testfile.txt", O_WRONLY | O_APPEND);
if (filedesc < 0) {
return -1;
}
if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) {
write(2, "There was an error writing to testfile.txt\n", 43);
return -1;
}
return 0;
}
memory map
を調べることをお勧めします。それがあなたの答えかもしれません。データベースにそれを保存するために他で20GBのファイルを処理しなければならなくなって、ファイルさえ開いていないとして。だからメモリマップを利用するような解決策。私はPython
でもそうしました。
Open()/ write()/ close()API呼び出しを使用して、出力バッファーサイズを試してみてください。つまり、一度に "many-many-bytes"バッファ全体を渡すのではなく、2、3回の書き込みを行います(つまり、TotalNumBytes/OutBufferSize)。 OutBufferSizeは4096バイトからメガバイトまでです。
別の方法 - WinAPI OpenFile/CreateFileを使用し、 このMSDNの記事 を使用してバッファリングを無効にします(FILE_FLAG_NO_BUFFERING)。そして このWriteFile()に関するMSDNの記事 は、最適なバッファサイズを知るためにドライブのブロックサイズを取得する方法を示しています。
とにかく、std :: ofstreamはラッパーであり、入出力操作をブロックしている可能性があります。 Nギガバイトの配列全体を移動するにも時間がかかることに注意してください。あなたが小さなバッファを書いている間、それはキャッシュに到達しそしてより速く動作します。
エクスプローラでディスクAからディスクBにコピーすると、WindowsはDMAを使用します。つまり、ほとんどのコピープロセスでは、CPUは基本的にディスクコントローラにデータの格納場所とデータの取得場所を指定すること以外は何もせず、チェーン全体の手順を省略します。データの - そして私はハードウェアを意味します。
何をあなたがするかは、CPUに大きな影響を与えます。 「[]を埋めるためのいくつかの計算」部分を紹介します。どちらが不可欠だと思います。 []を生成してから、[]から出力バッファにコピーし(これがfstream :: writeの動作です)、その後、再度生成します。
何をすべきか?マルチスレッド(私はあなたがマルチコアプロセッサを持っていることを願っています)
fstream
s自体はCストリームより遅くはありませんが、より多くのCPUを使用します(特にバッファリングが正しく設定されていない場合)。 CPUが飽和すると、I/Oレートが制限されます。
少なくともMSVC 2015の実装では、ストリームバッファが設定されていない場合、一度に1文字ずつ1文字を出力バッファにコピーします(streambuf::xsputn
を参照)。だからストリームバッファ(> 0)を設定してください。
このコードを使うとfstream
で1500MB/s(私のM.2 SSDの最高速度)の書き込み速度が得られます。
#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#endif
using namespace std;
using namespace std::chrono;
const size_t sz = 512 * 1024 * 1024;
const int numiter = 20;
const size_t bufsize = 1024 * 1024;
int main(int argc, char**argv)
{
unique_ptr<char[]> data(new char[sz]);
unique_ptr<char[]> buf(new char[bufsize]);
for (size_t p = 0; p < sz; p += 16) {
memcpy(&data[p], "BINARY.DATA.....", 16);
}
unlink("file.binary");
int64_t total = 0;
if (argc < 2 || strcmp(argv[1], "fopen") != 0) {
cout << "fstream mode\n";
ofstream myfile("file.binary", ios::out | ios::binary);
if (!myfile) {
cerr << "open failed\n"; return 1;
}
myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
for (int i = 0; i < numiter; ++i) {
auto tm1 = high_resolution_clock::now();
myfile.write(data.get(), sz);
if (!myfile)
cerr << "write failed\n";
auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
cout << tm << " ms\n";
total += tm;
}
myfile.close();
}
else {
cout << "fopen mode\n";
FILE* pFile = fopen("file.binary", "wb");
if (!pFile) {
cerr << "open failed\n"; return 1;
}
setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important
auto tm1 = high_resolution_clock::now();
for (int i = 0; i < numiter; ++i) {
auto tm1 = high_resolution_clock::now();
if (fwrite(data.get(), sz, 1, pFile) != 1)
cerr << "write failed\n";
auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
cout << tm << " ms\n";
total += tm;
}
fclose(pFile);
auto tm2 = high_resolution_clock::now();
}
cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n";
}
私はこのコードを他のプラットフォーム(Ubuntu、FreeBSD)で試してみたところ、入出力レートの違いはないが、CPU使用率は約8:1( fstream
が使用したCPUが8倍多くなりました)。それで、私が想像できるのは、私がより速いディスクであれば、fstream
の書き込みはstdio
のバージョンよりも早く遅くなるでしょう。
メモリマップファイルを使用してみてください。
ファイルストリームに高速に書き込みたい場合は、読み込みバッファを大きくすることができます。
wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);
また、ファイルに大量のデータを書き込むときは、物理的ではなく論理的にファイルサイズを拡張する方が速い場合があります。書き込む前に新しいスペースをゼロにします。論理的にファイルを拡張するのも賢い方法です。実際に必要以上にファイルを拡張しないようにするのが賢明です。 XFSシステムでXFS_IOC_RESVSP64
を指定してSetFileValidData
またはxfsctl
を呼び出すことにより、Windowsで論理ファイルの拡張子がサポートされます。
私のプログラムをgccでGNU/Linuxとmingwをwin 7とxpにコンパイルしてうまく動作した
私のプログラムを使って80 GBのファイルを作成するには、33行目を次のように変更します。
makeFile("Text.txt",1024,8192000);
プログラムを終了するとファイルは破壊され、実行中にファイルをチェックします
プログラムを変更したいだけのプログラムにする
1つはwindowsプログラム、もう1つはGNU/Linux用です。