web-dev-qa-db-ja.com

C ++でバイナリファイルを非常に速く書く

私は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倍速いです。だからこそ私はもっと早く行けるはずだと思います。

どのように私は私の執筆をスピードアップすることができますか?

216
Dominic Hofer

これは仕事をしました:

#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つの設定で実行します。

  • ノートパソコン、Core i7、SSD、Ubuntu 16.04、g ++バージョン7.2.0、-std = c ++ 11、-march = native -O3
  • デスクトップ、Core i7、SSD、Windows 10、/ Ox/Ob2/Oi/Ot/GT/GL/Gyを使用したVisual Studio 2017バージョン15.3.1

以下の測定値が得られました(明らかな異常値であるため、1MBの値を切り捨てた後): enter image description hereenter image description here option1とoption3の両方でSSDが最大になります。当時私のマシンではoption2が最速のコードだったので、これが見られるとは思っていませんでした。

TL; DR:私の測定値はFILEよりstd::fstreamを使うことを示しています。

194
Dominic Hofer

次のことを順番に試してください。

  • バッファサイズが小さい。一度に〜2 MiBを書くのは良いスタートかもしれません。私の最後のラップトップでは、〜512 KiBが最適な場所でしたが、私はまだSSDをテストしていません。

    注:非常に大きなバッファーはパフォーマンスが低下する傾向があることに気付きました。私は以前に512 KiBのバッファの代わりに16 MiBのバッファを使うことで速度の低下に気づいた。

  • ファイルを開くには_open(またはWindowsに適切な場合は_topen)を使用してから_writeを使用します。これはおそらく多くのバッファリングを避けるでしょうが、それは確実ではありません。

  • CreateFileWriteFileなどのWindows固有の関数を使用する。それは標準ライブラリでバッファリングを避けるでしょう。

23
Mehrdad

Std :: stream/FILE/deviceの間に違いはありません。バッファリングと非バッファリングの間.

また注意してください。

  • SSDドライブは、いっぱいになるにつれて遅くなる(転送速度が遅くなる)傾向があります。
  • SSDドライブは、古くなるにつれて(動作しないビットのために)遅くなる(転送速度が遅くなる)傾向があります。

コードが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つの方法に違いがあるかもしれません。上記。しかし、非バッファリングとバッファリング(バッファサイズを超える大きなチャンクを書き込むとき)を予想していても違いはありません。

編集2:

C++でファイルをコピーする最速の方法を試したことがありますか

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}
21
Martin York

最善の解決策は、ダブルバッファリングによる非同期書き込みを実装することです。

タイムラインを見てください。

------------------------------------------------>
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を呼び出すと、メソッドは戻ります。偽または例外をスローします。また、障害発生後にデータを書き込んだ場合でも、どの時点でバッファをいっぱいにしなくてもかまいません。ファイルが破損していて無駄になる可能性があります。

12
HandMadeOX

ファイルマッピング を試してみることをお勧めします。私は過去にUNIX環境でmmapを使っていましたが、私が達成できた高性能に感銘を受けました

10
Ralph

代わりに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でもそうしました。

8
cybertextron

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ギガバイトの配列全体を移動するにも時間がかかることに注意してください。あなたが小さなバッファを書いている間、それはキャッシュに到達しそしてより速く動作します。

6
Viktor Latypov

エクスプローラでディスクAからディスクBにコピーすると、WindowsはDMAを使用します。つまり、ほとんどのコピープロセスでは、CPUは基本的にディスクコントローラにデータの格納場所とデータの取得場所を指定すること以外は何もせず、チェーン全体の手順を省略します。データの - そして私はハードウェアを意味します。

何をあなたがするかは、CPUに大きな影響を与えます。 「[]を埋めるためのいくつかの計算」部分を紹介します。どちらが不可欠だと思います。 []を生成してから、[]から出力バッファにコピーし(これがfstream :: writeの動作です)、その後、再度生成します。

何をすべきか?マルチスレッド(私はあなたがマルチコアプロセッサを持っていることを願っています)

  • フォーク。
  • 1つのスレッドを使って[]データを生成する
  • []からディスクにデータを書き込むためにもう一方を使う
  • 2つの配列a1 []とa2 []が必要になり、それらを切り替えることができます。
  • スレッド間である種の同期が必要になります(セマフォ、メッセージキューなど)。
  • Mehrdadによって言及されている WriteFile 関数のような低レベルの、バッファされていない関数を使用してください。
3
dualed

fstreams自体は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のバージョンよりも早く遅くなるでしょう。

3
rustyx

メモリマップファイルを使用してみてください。

3
qehgt

ファイルストリームに高速に書き込みたい場合は、読み込みバッファを大きくすることができます。

wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);

また、ファイルに大量のデータを書き込むときは、物理的ではなく論理的にファイルサイズを拡張する方が速い場合があります。書き込む前に新しいスペースをゼロにします。論理的にファイルを拡張するのも賢い方法です。実際に必要以上にファイルを拡張しないようにするのが賢明です。 XFSシステムでXFS_IOC_RESVSP64を指定してSetFileValidDataまたはxfsctlを呼び出すことにより、Windowsで論理ファイルの拡張子がサポートされます。

1
user152949

私のプログラムをgccでGNU/Linuxmingwをwin 7とxpにコンパイルしてうまく動作した

私のプログラムを使って80 GBのファイルを作成するには、33行目を次のように変更します。

makeFile("Text.txt",1024,8192000);

プログラムを終了するとファイルは破壊され、実行中にファイルをチェックします

プログラムを変更したいだけのプログラムにする

1つはwindowsプログラム、もう1つはGNU/Linux用です。

http://mustafajf.persiangig.com/Projects/File/WinFile.cpp

http://mustafajf.persiangig.com/Projects/File/File.cpp

0
MostafaJF