Dom*_*fer 221 c++ io optimization performance file-io
我正在尝试将大量数据写入我的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();
}
Run Code Online (Sandbox Code Playgroud)
使用Visual Studio 2010进行编译并完全优化并在Windows7下运行,此程序最大可达20MB/s.让我感到困扰的是,Windows可以将文件从其他SSD复制到此SSD,速度介于150MB/s和200MB/s之间.所以至少快7倍.这就是为什么我认为我应该能够更快.
我有什么想法可以加快我的写作速度?
Dom*_*fer 202
这样做了:
#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;
}
Run Code Online (Sandbox Code Playgroud)
我只是在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;
}
Run Code Online (Sandbox Code Playgroud)
现在代码编译Visual Studio 2017和g ++ 7.2.0(现在是我的要求之一).我让代码运行两个设置:
其中给出了以下测量值(放弃1MB的值后,因为它们是明显的异常值):
option1和option3都是我的SSD的最大值.我没想到这会看到,因为option2曾经是我机器上最快的代码.
TL; DR:我的测量表明使用std::fstream了FILE.
Meh*_*dad 23
请按顺序尝试以下操作:
较小的缓冲区大小.一次写~2 MiB可能是一个好的开始.在我的最后一台笔记本电脑上,~512 KiB是最佳选择,但我尚未在我的SSD上测试过.
注意:我注意到非常大的缓冲区往往会降低性能.我注意到之前使用16-MiB缓冲区而不是512-KiB缓冲区的速度损失.
使用_open(或者_topen如果您想要Windows正确)打开文件,然后使用_write.这可能会避免很多缓冲,但不确定.
使用Windows的特定功能,如CreateFile和WriteFile.这将避免标准库中的任何缓冲.
Mar*_*ork 21
我看到std :: stream/FILE/device之间没有区别.在缓冲和非缓冲之间.
另请注意:
我看到代码在63秒内运行.
因此传输速率为:260M/s(我的SSD看起来比你的快一点).
64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/
= 16G
= 16G/63 = 260M/s
Run Code Online (Sandbox Code Playgroud)
通过从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);
}
Run Code Online (Sandbox Code Playgroud)
因此,C++流的工作速度与底层库允许的速度一样快.
但我认为将操作系统与构建在操作系统之上的应用程序进行比较是不公平的.应用程序不做任何假设(它不知道驱动器是SSD),因此使用操作系统的文件机制进行传输.
虽然操作系统不需要做任何假设.它可以告诉所涉及的驱动器类型,并使用最佳技术来传输数据.在这种情况下,直接内存到内存传输.尝试编写一个程序,将80G从内存中的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);
}
Run Code Online (Sandbox Code Playgroud)
这没有任何差别.
注意:我的驱动器是SSD驱动器,如果您有一个普通驱动器,您可能会看到上述两种技术之间的差异.但正如我所期望的那样,非缓冲和缓冲(当写入大块大于缓冲区大小时)没有任何区别.
您是否尝试过使用C++复制文件的最快方法
int main()
{
std::ifstream input("input");
std::ofstream output("ouptut");
output << input.rdbuf();
}
Run Code Online (Sandbox Code Playgroud)
Han*_*eOX 12
最好的解决方案是使用双缓冲实现异步写入.
看时间线:
------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|
Run Code Online (Sandbox Code Playgroud)
'F'表示缓冲区填充的时间,'W'表示将缓冲区写入磁盘的时间.所以在将缓冲区写入文件之间浪费时间的问题.但是,通过在单独的线程上实现写入,您可以立即开始填充下一个缓冲区,如下所示:
------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
|WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|
Run Code Online (Sandbox Code Playgroud)
F - 填充第一个缓冲区
f - 填充第二个缓冲区
W - 将第一个缓冲区写入文件
w - 将第二个缓冲区写入文件
_ - 等待操作完成
当填充缓冲区需要更复杂的计算(因此,更多时间)时,这种具有缓冲交换的方法非常有用.我总是实现一个隐藏异步写入的CSequentialStreamWriter类,所以对于最终用户来说,接口只有Write函数.
并且缓冲区大小必须是磁盘簇大小的倍数.否则,通过向2个相邻磁盘集群写入单个缓冲区,最终会导致性能下降.
写最后一个缓冲区.
当你最后一次调用Write函数时,你必须确保当前缓冲区的填充也应该写入磁盘.因此,CSequentialStreamWriter应该有一个单独的方法,比如Finalize(最终缓冲区刷新),它应该将最后一部分数据写入磁盘.
错误处理.
当代码开始填充第二个缓冲区,并且第一个缓冲区被写入单独的线程,但是由于某种原因写入失败时,主线程应该知道该失败.
------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|
Run Code Online (Sandbox Code Playgroud)
让我们假设一个CSequentialStreamWriter的接口有Write函数返回bool或抛出一个异常,因此在一个单独的线程上有一个错误,你必须记住那个状态,所以下次你在主线程上调用Write或Finilize时,该方法将返回假或将抛出异常.即使你在失败后提前写了一些数据,你停止填充缓冲区的时间并不重要 - 很可能文件会被破坏和无用.
您可以使用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;
}
Run Code Online (Sandbox Code Playgroud)
如果你决定使用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;
}
Run Code Online (Sandbox Code Playgroud)
我也建议你研究一下memory map.这可能是你的答案.一旦我不得不在其他地方处理一个20GB的文件,将它存储在数据库中,并且文件甚至没有打开.所以解决方案是利用moemory map.我这样做了Python.
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";
}
Run Code Online (Sandbox Code Playgroud)
我在其他平台(Ubuntu、FreeBSD)上尝试了这段代码,发现没有 I/O 速率差异,但CPU 使用率差异约为 8:1(fstream使用了8 倍的 CPU)。所以可以想象,如果我有一个更快的磁盘,fstream写入速度会比stdio版本更慢。
尝试使用open()/ write()/ close()API调用并尝试输出缓冲区大小.我的意思是不要一次传递整个"多个字节"的缓冲区,做一些写操作(即TotalNumBytes/OutBufferSize).OutBufferSize可以从4096字节到兆字节.
另一个尝试 - 使用WinAPI OpenFile/CreateFile并使用此MSDN文章关闭缓冲(FILE_FLAG_NO_BUFFERING).而在这个WriteFile的MSDN文章()演示了如何获取的块大小的驱动器知道最佳的缓冲区大小.
无论如何,std :: ofstream是一个包装器,I/O操作可能会阻塞.请记住,遍历整个N-gigabyte阵列也需要一些时间.在编写一个小缓冲区时,它会进入缓存并更快地运行.
| 归档时间: |
|
| 查看次数: |
171702 次 |
| 最近记录: |