用C ++编写一个二进制文件非常快

我正在尝试将大量数据写入我的SSD(固态驱动器)。 而巨额我的意思是80GB。

我浏览网页的解决scheme,但最好的我想出了这样的:

#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可以将文件从另一个SSD复制到这个SSD在150MB / s和200MB / s之间。 所以至less要快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最大。 另外值得注意的是,问题中的代码使用了一个100%的内核,而这个代码只使用了2-5%。

非常感谢大家。

更新 :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编译(这是我的一个要求)。 我让代码运行两个设置:

  • 笔记本电脑,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的值之后,因为它们是明显的exception值): 在这里输入图像描述 在这里输入图像描述 这两次选项1和选项3最多我的SSD。 我没有料到这一点,因为option2曾经是我机器上最快的代码。

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

依次尝试以下操作:

  • 较小的缓冲区大小。 一次写〜2MB可能是一个好的开始。 在我的最后一台笔记本电脑上,〜512 KiB是最佳select,但是我还没有在我的SSD上进行testing。

    注意:我注意到非常大的缓冲区往往会降低性能。 我注意到之前使用16-MiB缓冲区而不是512-KiB缓冲区会导致速度损失。

  • 使用_open (或_topen如果你想成为Windows正确的)打开文件,然后使用_write 。 这可能会避免很多缓冲,但不一定。

  • 使用特定于Windows的函数,如CreateFileWriteFile 。 这将避免标准库中的任何缓冲。

我看到std :: stream / FILE / device没有区别。 在缓冲和非缓冲之间。

另请注意:

  • SSD驱动器“倾向于”放慢速度(较低的传输速率),因为它们填满了。
  • 当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 ++stream的运行速度与底层库允许的一样快。

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

而操作系统不需要做任何假设。 它可以告诉所涉及的驱动器的types,并使用最佳的技术来传输数据。 在这种情况下直接内存传输到内存。 尝试编写一个程序,从内存中的1个位置复制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); } 

这没有任何分歧。

:我的驱动器是SSD驱动器,如果你有一个正常的驱动器,你可能会看到上述两种技术之间的差异。 但是正如我所预料的那样,非缓冲和缓冲(当写入比缓冲区大的大块时)没有任何区别。

编辑2:

你有没有试过用C ++复制文件的最快方法?

 int main() { std::ifstream input("input"); std::ofstream output("ouptut"); output << input.rdbuf(); } 

最好的解决scheme是使用双缓冲来实现asynchronous写入。

看时间线:

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

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

而缓冲区大小必须是磁盘簇大小的倍数。 否则,通过将单个缓冲区写入2个相邻的磁盘集群,会导致性能低下。

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

error handling。
虽然代码开始填充第二个缓冲区,并且第一个正在写入一个单独的线程,但由于某种原因写入失败,主线程应该知道该故障。

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

我们假设一个CSequentialStreamWriter的接口有Write函数返回bool或者抛出一个exception,因此在一个单独的线程上有一个错误,你必须记住这个状态,所以下次你在主线程上调用Write或Finilize时,方法会返回假或将会抛出exception。 即使您在失败之后提前写入了一些数据,您停止填充缓冲区的时间并不重要,这很可能是文件损坏和无用的情况。

我会build议尝试文件映射 。 过去我在UNIX环境中使用过mmap ,而且能够实现的高性能给我留下了深刻的印象

尝试使用open()/ write()/ close()API调用并尝试输出缓冲区大小。 我的意思是不要同时传递整个“多字节”缓冲区,做几个写操作(即TotalNumBytes / OutBufferSize)。 OutBufferSize可以从4096字节到兆字节。

另一个尝试 – 使用WinAPI OpenFile / CreateFile和使用此MSDN文章closures缓冲(FILE_FLAG_NO_BUFFERING)。 这个关于WriteFile()的MSDN文章展示了如何获得驱动器的块大小以了解最佳缓冲区大小。

无论如何,std :: ofstream是一个包装,可能会阻止I / O操作。 请记住遍历整个N字节数组也需要一些时间。 当你正在写一个小缓冲区时,它会进入caching并工作得更快。

你可以使用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; } 

我也build议你看看memory map 。 这可能是你的答案。 一旦我不得不处理一个20GB的文件在其他存储在数据库中,文件甚至没有打开。 所以解决scheme就是利用moemory映射。 我虽然在Python做到了这一点。

尝试使用内存映射文件。

如果您从资源pipe理器A中的某个磁盘复制到磁盘B,则Windows将使用DMA。 这意味着对于大多数的复制过程来说,除了告诉磁盘控制器放置哪里以及从中获取数据之外,CPU基本上不会做任何事情,从而消除链中的整个步骤,并且对于大量移动的数据 – 我的意思是硬件。

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

该怎么办? multithreading! (我希望你有一个多核处理器)

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

fstream本身并不比Cstream慢,但它们使用更多的CPU (尤其是如果缓冲没有正确configuration的话)。 当CPU饱和时,会限制I / O速率。

当没有设置stream缓冲区时,至lessMSVC 2015实现一次1个字符复制到输出缓冲区(请参阅streambuf::xsputn )。 所以一定要设置一个stream缓冲区(> 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版本的速度慢。

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

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

而且,在将大量数据写入文件时, 逻辑上扩展文件大小(而不是物理扩展)有时会更快,这是因为在逻辑上扩展文件时,文件系统在写入之前不会将新空间置零。 逻辑上扩展文件比实际需要的多,以防止大量文件扩展也是明智之举。 Windows上通过在XFS系统上调用SetFileValidDataxfsctlXFS_IOC_RESVSP64来支持逻辑文件扩展。

即时通讯编译我的程序在GNU / Linux中的GCC和 7赢得XP和工作良好

你可以使用我的程序,并创build一个80 GB的文件,只要将第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