273

SSD(ソリッドステートドライブ)に大量のデータを書き込もうとしています。そして、膨大な量で私は80GBを意味します。

私は解決策を求めてウェブを閲覧しましたが、私が思いついた最高のものはこれでした:

#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と完全な最適化を使用してコンパイルされ、Windows7で実行されるこのプログラムは、最大で約20MB/秒です。本当に気になるのは、Windowsが他のSSDからこのSSDに150MB/秒から200MB/秒の間でファイルをコピーできることです。したがって、少なくとも7倍速くなります。そういうわけで私は私がより速く行くことができるべきだと思います。

どうすれば文章をスピードアップできるか考えていますか?

4

12 に答える 12

269

これは仕事をしました(2012年に):

#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/秒で、SSDを最大限に活用できると思います。また、質問のコードは1つのコアを100%使用しましたが、このコードは2〜5%しか使用していません。

みなさん、どうもありがとうございました。

更新:5年が経過し、2017年になりました。コンパイラ、ハードウェア、ライブラリ、および私の要件が変更されました。そのため、コードにいくつかの変更を加え、いくつかの新しい測定を行いました。

最初にコードを作成します。

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

このコードは、VisualStudio2017およびg++7.2.0(新しい要件)でコンパイルされます。私は2つのセットアップでコードを実行しました:

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

これにより、次の測定値が得られました(明らかに外れ値であったため、1MBの値を破棄した後): option1とoption3の両方でSSDが最大になります。当時、option2は私の古いマシンで最速のコードだったので、これが表示されるとは思っていませんでした。ここに画像の説明を入力してください ここに画像の説明を入力してください

TL; DR:私の測定値はstd::fstream以上を使用することを示していますFILE

于 2012-07-19T16:11:11.490 に答える
26

次の順序で試してください。

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

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

  • を使用して_open(または_topenWindowsで正しくなりたい場合は)ファイルを開き、を使用します_write。これにより、多くのバッファリングを回避できる可能性がありますが、確実ではありません。

  • CreateFileおよびのようなWindows固有の関数を使用しますWriteFile。これにより、標準ライブラリでのバッファリングが回避されます。

于 2012-07-19T15:53:28.073 に答える
23

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

また注意してください:

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

コードが63秒で実行されているのがわかります。
したがって、転送速度は260M / sです(私のSSDはあなたの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は何の仮定もする必要はありませんが。関係するドライブのタイプを識別し、データを転送するための最適な手法を使用できます。この場合、メモリからメモリへのダイレクト転送。メモリ内のある場所から別の場所に80Gをコピーするプログラムを作成して、その速度を確認してください。

編集

下位レベルの呼び出しを使用するようにコードを変更しました
。つまり、バッファリングなしです。

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

これは何の違いもありませんでした。

:通常のドライブを使用している場合、上記の2つの手法の違いがわかる場合があります。しかし、私が予想したように、非バッファリングとバッファリング(バッファサイズよりも大きい大きなチャンクを書き込む場合)は違いがありません。

編集2:

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

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

    output << input.rdbuf();
}
于 2012-07-19T16:04:23.143 に答える
13

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

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

------------------------------------------------>
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-1番目のバッファをファイルに書き込む
w-2番目のバッファをファイルに書き込む
_-操作が完了するまで待機する

バッファスワップを使用するこのアプローチは、バッファを埋めるのにさらに複雑な計算が必要な場合(したがって、より多くの時間)に非常に役立ちます。私は常に非同期書き込みを内部に隠すCSequentialStreamWriterクラスを実装しているため、エンドユーザーの場合、インターフェイスには書き込み関数しかありません。

また、バッファサイズはディスククラスタサイズの倍数である必要があります。そうしないと、2つの隣接するディスククラスターに単一のバッファーを書き込むことにより、パフォーマンスが低下することになります。

最後のバッファを書き込みます。
最後に書き込み関数を呼び出すときは、現在のバッファーがいっぱいになっていることをディスクにも書き込む必要があることを確認する必要があります。したがって、CSequentialStreamWriterには、データの最後の部分をディスクに書き込む必要がある、別のメソッド、たとえばFinalize(最終バッファーフラッシュ)が必要です。

エラー処理。
コードが2番目のバッファーをいっぱいにし始め、1番目のバッファーが別のスレッドに書き込まれているが、何らかの理由で書き込みが失敗した場合、メインスレッドはその失敗を認識している必要があります。

------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|

CSequentialStreamWriterのインターフェイスに書き込み関数がboolを返すか、例外をスローし、別のスレッドでエラーが発生した場合、その状態を覚えておく必要があると仮定します。次にメインスレッドでWriteまたはFinilizeを呼び出すと、メソッドは次のようになります。 Falseまたは例外をスローします。また、障害の後にデータを先に書き込んだとしても、どの時点でバッファの充填を停止したかは実際には重要ではありません。ファイルが破損して役に立たない可能性があります。

于 2014-08-28T00:56:25.310 に答える
11

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

于 2012-07-19T21:35:11.867 に答える
8

代わりに使用FILE*して、得られたパフォーマンスを測定できますか?いくつかのオプションは、次fwrite/writeの代わりに使用することですfstream

#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た。

于 2012-07-19T15:50:18.370 に答える
8

fstream■Cストリーム自体よりも低速ではありませんが、より多くのCPUを使用します(特にバッファリングが適切に構成されていない場合)。CPUが飽和状態になると、I/Oレートが制限されます。

ストリームバッファが設定されていない場合、少なくともMSVC 2015実装は一度に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)で試してみたところ、I / Oレートの違いはありませんでしたが、CPU使用率の違いは約8:1(8倍のCPUを使用fstream)でした。したがって、私がより高速なディスクを使用している場合、書き込みはバージョンよりも早く遅くなることが想像できます。fstreamstdio

于 2016-08-23T09:43:08.677 に答える
6

open()/ write()/ close()API呼び出しを使用して、出力バッファーサイズを試してみてください。つまり、「many-many-bytes」バッファ全体を一度に渡すのではなく、2、3回の書き込み(つまり、TotalNumBytes / OutBufferSize)を実行します。OutBufferSizeは、4096バイトからメガバイトまで可能です。

別の試み-WinAPIOpenFile/ CreateFileを使用し、このMSDNの記事を使用してバッファリング(FILE_FLAG_NO_BUFFERING)をオフにします。また、WriteFile()に関するこのMSDNの記事では、ドライブのブロックサイズを取得して最適なバッファサイズを知る方法を示しています。

とにかく、std :: ofstreamはラッパーであり、I/O操作がブロックされている可能性があります。Nギガバイト配列全体をトラバースするのにも時間がかかることに注意してください。小さなバッファを書き込んでいる間、それはキャッシュに到達し、より速く動作します。

于 2012-07-19T15:25:44.697 に答える
3

エクスプローラーでディスクAからディスクBに何かをコピーする場合、WindowsはDMAを採用します。つまり、ほとんどのコピープロセスでは、CPUは基本的に、ディスクコントローラにデータの配置と取得を指示する以外に何もせず、チェーンのステップ全体を排除し、大量の移動に最適化されていないことを意味します。データの-そして私はハードウェアを意味します。

あなたすることはCPUをたくさん含みます。「[]を埋めるためのいくつかの計算」の部分を指摘したいと思います。これは不可欠だと思います。a []を生成し、次にa []から出力バッファ(fstream :: writeが行うこと)にコピーしてから、再度生成します。

何をすべきか?マルチスレッド!(マルチコアプロセッサをお持ちだといいのですが)

  • フォーク。
  • 1つのスレッドを使用して[]データを生成します
  • もう一方を使用して、a[]からディスクにデータを書き込みます
  • 2つの配列a1[]とa2[]が必要であり、それらを切り替える必要があります
  • スレッド間で何らかの同期が必要になります(セマフォ、メッセージキューなど)。
  • Mehrdadが言及したWriteFile関数のような、バッファリングされていない低レベルの関数を使用します
于 2012-07-19T16:33:00.623 に答える
2

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

于 2012-07-19T15:43:08.600 に答える
1

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

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

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

于 2013-03-02T18:17:21.270 に答える
0

私のプログラムをGNU/Linuxのgccでコンパイルし、 win7でmingwをコンパイルしてxpを勝ち取りました。

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

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

プログラムを終了すると、ファイルは破棄され、実行中にファイルを確認します

必要なプログラムを作成するには、プログラムを変更するだけです。

1つ目はWindowsプログラムで、2つ目はGNU/Linux用です。

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

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

于 2012-07-25T07:19:52.420 に答える