我正试图将大量的数据写入我的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/s左右。真正困扰我的是,Windows可以以150MB/s到200MB/s之间的速度将文件从另一个SSD复制到这个SSD。至少快7倍。这就是为什么我认为我应该能跑得更快。

有什么办法可以加快我的写作速度吗?


当前回答

我建议尝试文件映射。我过去在UNIX环境中使用过mmapin,它所能实现的高性能给我留下了深刻的印象

其他回答

最好的解决方案是使用双缓冲实现异步写入。

看看时间轴:

------------------------------------------------>
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 -填充第一个缓冲区 F -填充第二缓冲区 写入文件的第一个缓冲区 写入第二个缓冲区文件 _ -等待操作完成

当填充缓冲区需要更复杂的计算(因此需要更多时间)时,使用缓冲区交换的这种方法非常有用。 我总是实现一个CSequentialStreamWriter类,它隐藏了异步写入,所以对于最终用户来说,接口只有写入函数。

缓冲区大小必须是磁盘集群大小的倍数。否则,通过将一个缓冲区写入两个相邻的磁盘集群,您将最终获得较差的性能。

正在写入最后一个缓冲区。 当您最后一次调用Write函数时,必须确保当前正在被填充的缓冲区也应该写入磁盘。因此CSequentialStreamWriter应该有一个单独的方法,比如Finalize(最后的缓冲区刷新),它应该把最后一部分数据写入磁盘。

错误处理。 当代码开始填充第二个缓冲区时,第一个缓冲区正在另一个线程上写入,但是由于某种原因写入失败了,主线程应该知道这个失败。

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

让我们假设CSequentialStreamWriter的接口有Write函数返回bool值或抛出异常,因此在一个单独的线程上有一个错误,你必须记住那个状态,所以下次你在主线程上调用Write或finalize时,该方法将返回False或抛出异常。在什么时候停止填充缓冲区并不重要,即使在失败后提前写入了一些数据,文件很可能会损坏并且无用。

这是成功的(在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/s,我认为这将耗尽我的SSD。同样值得注意的是,问题中的代码使用了一个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;
}

此代码使用Visual Studio 2017和g++ 7.2.0(新需求)编译。 我用两个设置运行代码:

笔记本电脑,Core i7, SSD, Ubuntu 16.04, g++ Version 7.2.0带有-std=c++11 -march=native -O3 桌面,Core i7, SSD, Windows 10, Visual Studio 2017 Version 15.3.1,带有/Ox /Ob2 /Oi /Ot /GT /GL /Gy

它给出了以下测量值(在抛弃1MB的值后,因为它们是明显的异常值): 两次option1和option3都将耗尽我的SSD。我没有期望看到这个,因为option2曾经是我的旧机器上最快的代码。

TL;DR:我的测量表明在文件上使用std::fstream。

我建议尝试文件映射。我过去在UNIX环境中使用过mmapin,它所能实现的高性能给我留下了深刻的印象

我编译我的程序在gcc在GNU/Linux和mingw在win7和winxp和工作良好

你可以用我的程序创建一个80gb的文件,只需要把第33行改为

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

当退出程序时,文件将被销毁,然后检查文件时,它正在运行

要得到你想要的程序,只需改变程序

第一个是windows程序,第二个是GNU/Linux程序

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

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

fstreams本身并不比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 (fstream使用了8倍多的CPU)。所以可以想象,如果我有一个更快的磁盘,fstream写速度会比stdio版本慢。