我正在研究一个简单的解析器,在分析时我观察到瓶颈在于...文件读取!我提取了非常简单的测试来比较 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 毫秒),可能是因为文件被缓存了...这就是为什么打开读取的文件不是使用代码。
与 FILE*
相比,为什么使用 fstream
仅读取一个二进制数据的 blob 会如此缓慢?
编辑 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
EDIT 4 出于某种原因,此问题被标记为重复。我想指出我根本不使用 printf
,我只使用 std::cout
来写时间。 read
部分使用的文件是 write
部分的输出,使用不同的名称复制以避免缓存
最佳答案
在 Linux 上,对于这么大的数据集,fwrite
的实现似乎要高效得多,因为它使用 write
而不是 写v
。
我不确定为什么 writev
比 write
慢得多,但这似乎就是区别所在。而且我绝对没有真正的理由说明为什么 fstream
在这种情况下需要使用该构造。
这可以通过使用 strace ./a.out
很容易看到(其中 a.out
是测试这个的程序)。
输出:
F流:
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 毫秒。
所以,我的结论是,有时,关闭文件会使它刷新到磁盘。在其他情况下,它不会。我还是不明白为什么……
关于c++ - 为什么 std::ostream 这么慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26095160/