Why are std::fstreams so slow?
我正在研究一个简单的解析器,当分析时,我发现瓶颈在…文件读取!我提取了非常简单的测试来比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <stdio.h> #include <chrono> #include <fstream> #include <iostream> #include <functional> void measure(const std::string& test, std::function<void()> function) { auto start_time = std::chrono::high_resolution_clock::now(); function(); auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start_time); std::cout<<test<<""<<static_cast<double>(duration.count()) * 0.000001<<" ms"<<std::endl; } #define BUFFER_SIZE (1024 * 1024 * 1024) int main(int argc, const char * argv[]) { auto buffer = new char[BUFFER_SIZE]; memset(buffer, 123, BUFFER_SIZE); measure("FILE* write", [buffer]() { FILE* file = fopen("test_file_write","wb"); fwrite(buffer, 1, BUFFER_SIZE, file); fclose(file); }); measure("FILE* read", [buffer]() { FILE* file = fopen("test_file_read","rb"); fread(buffer, 1, BUFFER_SIZE, file); fclose(file); }); measure("fstream write", [buffer]() { std::ofstream stream("test_stream_write", std::ios::binary); stream.write(buffer, BUFFER_SIZE); }); measure("fstream read", [buffer]() { std::ifstream stream("test_stream_read", std::ios::binary); stream.read(buffer, BUFFER_SIZE); }); delete[] buffer; } |
在我的计算机上运行此代码的结果是:
1 2 3 4 | FILE* write 1388.59 ms FILE* read 1292.51 ms fstream write 3105.38 ms fstream read 3319.82 ms |
为什么在使用
编辑1:我更新了代码和时间。抱歉耽搁了!
编辑2:我添加了命令行和新结果(与以前的非常相似!)
1 2 3 4 5 6 | $ clang++ main.cpp -std=c++11 -stdlib=libc++ -O3 $ ./a.out FILE* write 1417.9 ms FILE* read 1292.59 ms fstream write 3214.02 ms fstream read 3052.56 ms |
根据第二次运行的结果:
1 2 3 4 5 | $ ./a.out FILE* write 1428.98 ms FILE* read 196.902 ms fstream write 3343.69 ms fstream read 2285.93 ms |
在读取
编辑3:我将代码简化为:
1 2 3 4 5 6 | FILE* file = fopen("test_file_write","wb"); fwrite(buffer, 1, BUFFER_SIZE, file); fclose(file); std::ofstream stream("test_stream_write", std::ios::binary); stream.write(buffer, BUFFER_SIZE); |
启动了分析器。似乎
1 2 3 4 5 6 | Running Time Self Symbol Name 3266.0ms 66.9% 0,0 std::__1::basic_ostream<char, std::__1::char_traits<char> >::write(char const*, long) 3265.0ms 66.9% 2145,0 std::__1::basic_streambuf<char, std::__1::char_traits<char> >::xsputn(char const*, long) 1120.0ms 22.9% 7,0 std::__1::basic_filebuf<char, std::__1::char_traits<char> >::overflow(int) 1112.0ms 22.7% 2,0 fwrite 1127.0ms 23.0% 0,0 fwrite |
出于某种原因,编辑4将此问题标记为重复。我想指出的是,我根本不使用
在Linux上,对于这一大数据集,
我不知道为什么
这可以通过使用
输出:
Fstream:
1 2 3 4 5 6 7 | clock_gettime(CLOCK_REALTIME, {1411978373, 114560081}) = 0 open("test", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3 writev(3, [{NULL, 0}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1073741824}], 2) = 1073741824 close(3) = 0 clock_gettime(CLOCK_REALTIME, {1411978386, 376353883}) = 0 write(1,"fstream write 13261.8 ms ", 25fstream write 13261.8 ms) = 25 |
文件*:
1 2 3 4 5 6 | clock_gettime(CLOCK_REALTIME, {1411978386, 930326134}) = 0 open("test", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3 write(3,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1073741824) = 1073741824 clock_gettime(CLOCK_REALTIME, {1411978388, 584197782}) = 0 write(1,"FILE* write 1653.87 ms ", 23FILE* write 1653.87 ms) = 23 |
我没有漂亮的固态硬盘驱动器,所以我的机器会慢一点-或者其他东西在我的情况下慢一点。
正如JanHudec所指出的,我误解了结果。我刚刚写的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/uio.h> #include <unistd.h> #include <iostream> #include <cstdlib> #include <cstring> #include <functional> #include <chrono> void measure(const std::string& test, std::function<void()> function) { auto start_time = std::chrono::high_resolution_clock::now(); function(); auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start_time); std::cout<<test<<""<<static_cast<double>(duration.count()) * 0.000001<<" ms"<<std::endl; } #define BUFFER_SIZE (1024 * 1024 * 1024) int main() { auto buffer = new char[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); measure("writev", [buffer]() { int fd = open("test", O_CREAT|O_WRONLY); struct iovec vec[] = { { NULL, 0 }, { (void *)buffer, BUFFER_SIZE } }; writev(fd, vec, sizeof(vec)/sizeof(vec[0])); close(fd); }); measure("write", [buffer]() { int fd = open("test", O_CREAT|O_WRONLY); write(fd, buffer, BUFFER_SIZE); close(fd); }); } |
实际的
两种情况下的结果几乎相同,并且比问题中的
编辑:
在我的机器上,现在,如果您在写入后添加
但是,我可以使用此代码更快地编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/uio.h> #include <unistd.h> #include <iostream> #include <cstdlib> #include <cstring> #include <functional> #include <chrono> void measure(const std::string& test, std::function<void()> function) { auto start_time = std::chrono::high_resolution_clock::now(); function(); auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - start_time); std::cout<<test<<""<<static_cast<double>(duration.count()) * 0.000001<<" ms"<<std::endl; } #define BUFFER_SIZE (1024 * 1024 * 1024) int main() { auto buffer = new char[BUFFER_SIZE]; memset(buffer, 0, BUFFER_SIZE); measure("writev", [buffer]() { int fd = open("test", O_CREAT|O_WRONLY, 0660); struct iovec vec[] = { { NULL, 0 }, { (void *)buffer, BUFFER_SIZE } }; writev(fd, vec, sizeof(vec)/sizeof(vec[0])); close(fd); }); measure("write", [buffer]() { int fd = open("test", O_CREAT|O_WRONLY, 0660); write(fd, buffer, BUFFER_SIZE); close(fd); }); } |
给出约650-900 ms的时间。
我还可以编辑原始程序,为
我还添加了这个方法:
1 2 3 4 5 6 | measure("fstream write (new)", [buffer]() { std::ofstream* stream = new std::ofstream("test", std::ios::binary); stream->write(buffer, BUFFER_SIZE); // Intentionally no delete. }); |
然后这里也需要1000毫秒。
所以,我的结论是,有时候,关闭文件会使它刷新到磁盘上。在其他情况下,我不明白为什么…
tl;dr:在编写代码之前,请尝试将其添加到代码中:
1 2 3 | const size_t bufsize = 256*1024; char buf[bufsize]; mystream.rdbuf()->pubsetbuf(buf, bufsize); |
使用
相反,禁用流缓冲会显著降低性能。在没有设置缓冲区的情况下,MSVC实现至少一次将1个字符复制到
注意:您可以在这里找到一个完整的示例应用程序。
与其他答案相反,大型文件读取的一个大问题来自于C标准库的缓冲。尝试使用低级别的
C库的文件缓冲对于读取或写入小数据块(小于磁盘块大小)很有用。
在Windows上,在读取和写入原始视频流时,我的性能几乎提高了3倍。
我还使用本机OS(Win32)API调用打开了该文件,并告诉操作系统不要缓存该文件,因为这涉及到另一个副本。
流在mac、旧的实现或设置上以某种方式中断。
旧的安装程序可能会导致文件写入到exe目录和用户目录中的流中,除非您有2个磁盘或其他不同的设置,否则这不会有任何区别。
在我糟糕的远景里我得到正常缓冲+未缓存:C++ 201103文件*写入4756 ms文件*读取5007 msfstream写入5526 msfstream读取5728 ms
普通缓冲区+缓存:C++ 201103文件*写入4747 ms文件*读取454 msfstream写入5490 msfstream读取396 ms
大缓冲+缓存:C++ 201103第五运行:文件*写入4760 ms文件*读取446 msfstream写入5278 msfstream读取369 ms
这表明文件写入速度比fstream快,但读取速度比fstream慢。但所有的数字都在10%以内。
尝试向流中添加更多的缓冲,看看这是否有帮助。
1 2 3 | const int MySize = 1024*1024; char MrBuf[MySize]; stream.rdbuf()->pubsetbuf(MrBuf, MySize); |
文件的等效值为
1 2 3 | const int MySize = 1024*1024; if (!setvbuf ( file , NULL , _IOFBF , MySize )) DieInDisgrace(); |
对谁感兴趣的旁注。主要关键字是Windows 2016 Server/CloseHandle。
在我们的应用程序中,我们在Win2016服务器上发现了一个严重的错误。
我们在每个Windows版本下的标准代码采用:(ms)
时间createfile/setfilepointer 1 writefile 0 closehandle 0
在Windows 2016上,我们得到:
时间createfile/setfilepointer 1 writefile 0 closehandle 275
时间随着文件的尺寸而增长,这是荒谬的。
经过大量调查(我们首先发现"closehandle"是罪魁祸首…)我们发现,在windows2016下ms在close函数中附加了一个"hook",该函数会触发"windows defender"扫描所有文件并阻止返回,直到完成。(换句话说,扫描是同步的,这是纯粹的疯狂)。
当我们在文件的"defender"中添加排除时,一切都正常。我认为是一个糟糕的设计,没有防病毒停止正常的文件活动在程序空间内扫描文件。(微软可以做到他们有能力做到。)