我正在研究一个简单的解析器,在进行性能分析时,我观察到瓶颈在...文件读取!在阅读大量数据时,我提取了非常简单的测试来比较fstreams
和FILE*
的性能:
#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;
}
在我的机器上运行此代码的结果是:
FILE* write 1388.59 ms
FILE* read 1292.51 ms
fstream write 3105.38 ms
fstream read 3319.82 ms
fstream
写入/读取比FILE*
写入/读取慢大约2倍!这是在阅读大量数据的同时,没有fstreams
的任何解析或其他功能。我在Mac OS,Intel I7 2.6GHz,16GB 1600 MHz Ram,SSD驱动器上运行代码。请注意,再次运行相同的代码FILE* read
的时间非常短(大约200毫秒)可能是因为文件被缓存...这就是为什么打开用于读取的文件不是使用代码创建的。
与fstream
相比,使用FILE*
只读取一大块二进制数据的原因是什么?
编辑1:我更新了代码和时间。抱歉延误了!
编辑2:我添加了命令行和新结果(与之前的结果非常相似!)
$ 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
按照第二轮的结果:
$ ./a.out
FILE* write 1428.98 ms
FILE* read 196.902 ms
fstream write 3343.69 ms
fstream read 2285.93 ms
在阅读FILE*
和stream
时,看起来文件会被缓存,因为时间减少,两者的数量相同。
编辑3:我将代码缩减为:
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);
启动了探查器。似乎stream
在xsputn
函数中花费了大量时间,并且实际的write
调用具有相同的持续时间(应该是,它具有相同的功能...... )
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 由于某种原因,此问题被标记为重复。我想指出我根本不使用printf
,我只使用std::cout
来写时间。 read
部分中使用的文件是write
部分的输出,使用不同的名称复制以避免缓存
答案 0 :(得分:18)
在Linux上,似乎对于这一大量数据,fwrite
的实现效率更高,因为它使用write
而不是writev
。
我不确定为什么writev
比write
慢得多,但这似乎是差异所在。在这种情况下,我认为fstream
为什么需要使用该构造,我完全没有理由。
使用strace ./a.out
(其中a.out
是测试此程序的程序)可以很容易地看出这一点。
输出:
fstream的:
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\n", 25fstream write 13261.8 ms) = 25
文件*:
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\n", 23FILE* write 1653.87 ms) = 23
我没有他们看中的SSD驱动器,所以我的机器会慢一点 - 或者其他东西在我的情况下更慢。
正如Jan Hudec指出的那样,我误解了结果。我刚刚写了这个:
#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);
});
}
实际的fstream
实现做了一些愚蠢的事情 - 可能是以小块,某处,某种方式或类似的方式复制整个数据。我会试着进一步了解。
结果对于这两种情况几乎完全相同,并且比问题中的fstream
和FILE*
变体更快。
编辑:
在我的计算机上,现在看来,如果您在写入后添加fclose(file)
,则fstream
和FILE*
所需的时间大致相同我的系统,写入1GB约13秒 - 使用旧式旋转磁盘类型驱动器,而不是SSD。
然而,我可以使用此代码更快地写入:
#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毫秒的时间。
我还可以编辑原始程序,为fwrite
提供大约1000毫秒的时间 - 只需删除fclose
即可。
我还添加了这个方法:
measure("fstream write (new)", [buffer]()
{
std::ofstream* stream = new std::ofstream("test", std::ios::binary);
stream->write(buffer, BUFFER_SIZE);
// Intentionally no delete.
});
然后它也需要大约1000毫秒。
所以,我的结论是,不知何故,有时候,关闭文件会使它刷新到磁盘。在其他情况下,它没有。我还是不明白为什么......
答案 1 :(得分:3)
TL; DR:在编写代码之前,请尝试将其添加到代码中:
const size_t bufsize = 256*1024;
char buf[bufsize];
mystream.rdbuf()->pubsetbuf(buf, bufsize);
使用fstream
的大型文件时,请务必 使用流式缓冲区 。
违反直觉,禁用流缓冲会大大降低性能。当没有设置缓冲区时,至少MSVC实现将 1个字符一次复制到filebuf
(参见streambuf::xsputn()
),这可以使您的应用程序受CPU限制,这将导致I / O率降低。
注意:您可以找到完整的示例应用here。
答案 2 :(得分:1)
在MAC,旧实现或设置上以某种方式断开了流。
旧的设置可能会导致FILE写入exe目录和用户目录中的流,除非您有2个磁盘或其他不同的设置,否则这应该没有任何区别。
在我糟糕的Vista上,我得到了
正常缓冲区+未缓存:
C ++ 201103
FILE *写入4756 ms
文件*读取5007毫秒
fstream写5526毫秒
fstream读取5728 ms
正常缓冲区+缓存:
C ++ 201103
FILE *写4747 ms
文件*读取454毫秒
fstream写5490毫秒
fstream读取396毫秒
大缓冲区+缓存:
C ++ 201103
第五轮:
文件*写4760毫秒
文件*读取446毫秒
fstream写5278毫秒
fstream读取369 ms
这表明FILE写入比fstream快,但读取速度比fstream慢......但是所有数字都在相差10%左右。
尝试在流中添加更多缓冲,看看是否有帮助。
const int MySize = 1024*1024;
char MrBuf[MySize];
stream.rdbuf()->pubsetbuf(MrBuf, MySize);
FILE的等价物是
const int MySize = 1024*1024;
if (!setvbuf ( file , NULL , _IOFBF , MySize ))
DieInDisgrace();
答案 3 :(得分:1)
与其他答案相反,大文件读取的一个大问题来自C标准库的缓冲。尝试在大块(1024KB)中使用低级别read
/ write
调用,并查看性能跳转。
C库的文件缓冲对于读取或写入小块数据(小于磁盘块大小)非常有用。
在Windows上,在读取和写入原始视频流时,我获得了几乎3倍的性能提升,降低了文件缓冲。
我还使用本机操作系统(win32)API调用打开了文件,并告诉操作系统不要缓存文件,因为这涉及另一个副本。
答案 4 :(得分:0)
对他们感兴趣的附注。 主要关键字是Windows 2016 server / CloseHandle。
在我们的应用中,我们在win2016服务器上发现了一个NASTY错误。
我们在每个Windows版本下的标准代码都需要:(ms)
时间CreateFile / SetFilePointer 1 WriteFile 0 CloseHandle 0
在Windows 2016上我们得到了:时间CreateFile / SetFilePointer 1 WriteFile 0 CloseHandle 275
随着文件维度的增长,时间越来越长。这就是ABSURD。
经过大量调查(我们首先发现“CloseHandle”是罪魁祸首......)我们发现在windows2016下,MS在关闭函数中附加了一个“钩子”,触发“Windows Defender”扫描所有文件并阻止返回直到完成。 (换句话说,扫描是同步的,即PURE MADNESS)。
当我们在“Defender”中为我们的文件添加排除时,一切正常。 我认为是 BAD 设计,没有防病毒软件会阻止普通文件活动的INSIDE程序空间来扫描文件。 (MS可以这样做,因为他们有能力这样做。)