我正试图将大量的数据写入我的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,它所能实现的高性能给我留下了深刻的印象

这是成功的(在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。

如果你在资源管理器中将一些东西从磁盘A复制到磁盘B, Windows使用DMA。这意味着对于大多数的复制过程,CPU基本上什么也不做,只是告诉磁盘控制器在哪里放置数据,并从哪里获取数据,从而消除了整个链中的一个步骤,而且这个步骤根本不适合移动大量数据(我指的是硬件)。

你所做的事情涉及到CPU很多。 我想给你指出“一些计算来填充[]”部分。我认为这是必要的。你生成一个[],然后你从一个[]复制到一个输出缓冲区(这就是fstream::write所做的),然后你再次生成,等等。

怎么办呢?多线程!(我希望你有一个多核处理器)

叉。 使用一个线程生成一个[]数据 使用另一个将a[]中的数据写入磁盘 您将需要两个数组a1[]和a2[],并在它们之间进行切换 您将需要在线程(信号量、消息队列等)之间进行某种同步。 使用较低级别的、无缓冲的函数,比如Mehrdad提到的WriteFile函数

我发现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++流的工作速度与底层库所允许的一样快。

但我认为将操作系统与构建在操作系统之上的应用程序进行比较是不公平的。应用程序不能做任何假设(它不知道驱动器是SSD),因此使用操作系统的文件机制进行传输。

而操作系统不需要做任何假设。它可以告诉所涉及的驱动器的类型,并使用最佳的技术来传输数据。在这种情况下,是直接内存到内存的传输。试着写一个程序,把80G从内存的一个位置复制到另一个位置,看看有多快。

Edit

我修改了我的代码,以使用较低级别的调用: 没有缓冲。

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

这没什么区别。

注意:我的驱动器是SSD驱动器,如果你有一个普通的驱动器,你可能会看到上面两种技术之间的区别。但正如我所期望的那样,非缓冲和缓冲(当写入大于缓冲区大小的大块时)没有区别。

编辑2:

你试过用c++复制文件的最快方法吗

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

    output << input.rdbuf();
}

如果你想快速写入文件流,那么你可以让stream读缓冲区更大:

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

此外,当向文件写入大量数据时,逻辑扩展文件大小有时比物理扩展文件大小更快,这是因为在逻辑扩展文件时,文件系统在写入之前不会将新空间归零。明智的做法是在逻辑上对文件进行比实际需要更多的扩展,以防止大量的文件扩展。在Windows上通过调用SetFileValidData或xfsctl在XFS系统上使用XFS_IOC_RESVSP64支持逻辑文件扩展名。