【问题标题】:Why are std::fstreams so slow?为什么 std::fstreams 这么慢?
【发布时间】:2014-11-23 13:35:24
【问题描述】:

我正在研究一个简单的解析器,在分析时我观察到瓶颈在于...文件读取!我提取了一个非常简单的测试来比较fstreamsFILE* 在读取大量数据时的性能:

#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 仅读取一个二进制数据的速度如此之慢?

编辑 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);

并启动分析器。似乎streamxsputn 函数中花费了大量时间,而实际的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)上,您的结果将严重偏斜(您无法直接从 HDD 读取 1GB例如,在 200 毫秒内。)
  • 你的前两个measures 不应该是至少fcloseFILE *吗?
  • @rapptz - 如果你每秒只能构造 10 个字符串流,那你的日子会很悲惨!

标签: c++ performance


【解决方案1】:

在 Linux 上,对于这么大的数据集,fwrite 的实现似乎要高效得多,因为它使用 write 而不是 writev

我不确定为什么writevwrite 慢得多,但这似乎就是区别所在。而且我完全看不出为什么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 实现做了一些愚蠢的事情——可能以小块的形式复制整个数据,在某个地方以某种方式复制,或者类似的东西。我会尝试进一步了解。

这两种情况的结果几乎相同,并且比问题中的fstreamFILE* 变体更快。

编辑:

现在,在我的机器上,如果在写入后添加fclose(file)fstreamFILE* 所需的时间大致相同 - 在我的系统上,大约 13 秒写入 1GB - 使用旧式旋转磁盘类型驱动器,而不是 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 毫秒。

所以,我的结论是,有时,关闭文件会使它刷新到磁盘。在其他情况下,它不会。我还是不明白为什么……

【讨论】:

  • 总之,一个糟糕的实现的工件......我猜那是 libstdc++,那么 libc++ 呢?
  • 我记得几年前我很惊讶 Linux 上的 writev()write() 慢得多。甚至不值得将 writev() 用于其既定目的,即通过一次调用写入多个缓冲区。
  • 您是否尝试过更改 fstream 上的文件缓冲区?
  • 我不明白这是如何回答这个问题的。在我的电脑上,时间是可比的(几乎相同)
  • -1。您正在跳出与您展示的实验不同的结论。您未能将实际的系统调用与库开销区分开来,并假设系统调用是未经证实的,因为系统调用最终会在内核端调用相同的文件方法(writev 只会在请求被分块时效率较低很多位,但这里不是)。
【解决方案2】:

流在 MAC、旧实现或设置上以某种方式中断。

旧的设置可能会导致 FILE 写入 exe 目录和用户目录中的流,除非您有 2 个磁盘或其他不同的设置,否则这不会有任何区别。

在我糟糕的 Vista 上,我得到了 普通缓冲区+未缓存:
C++ 201103
FILE* 写入 4756 毫秒
文件* 读取 5007 毫秒
fstream 写入 5526 毫秒
fstream 读取 5728 毫秒

普通缓冲区+缓存:
C++ 201103
FILE* 写入 4747 毫秒
文件* 读取 454 毫秒
fstream 写入 5490 毫秒
fstream 读取 396 毫秒

大缓冲区+缓存:
C++ 201103
第 5 次运行:
FILE* 写入 4760 毫秒
文件* 读取 446 毫秒
fstream 写入 5278 毫秒
fstream 读取 369 毫秒

这表明 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();

【讨论】:

  • 我得到FILE* read()的结果在应用程序第一次运行后加快了很多。看起来操作系统正在那里做一些缓存。
【解决方案3】:

与其他答案相反,大文件读取的一个大问题来自 C 标准库的缓冲。尝试在大块 (1024KB) 中使用低级 read/write 调用,看看性能提升。

C 库的文件缓冲对于读取或写入小块数据(小于磁盘块大小)很有用。

在 Windows 上,我在读取和写入原始视频流时丢弃文件缓冲的性能几乎提高了 3 倍。

我还使用本机操作系统 (win32) API 调用打开了该文件,并告诉操作系统不要缓存该文件,因为这涉及另一个副本。

【讨论】:

  • 这并不能解释 fread/fwrite 和 std::fstream 之间的区别;两个 API 都使用缓冲。
【解决方案4】:

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

【讨论】:

  • 我可以确认。当将 bufsize 设置为 256*1024 时,我在使用 VS2015-update3 的 Windows 7 上读取一个 25MB 文件的时间从 0.15 秒下降到 0.02 秒。
  • 为什么是 256 * 1024?
【解决方案5】:

对谁感兴趣的旁注。 主要关键字是Windows 2016 server /CloseHandle。

在我们的应用中,我们在 win2016 服务器上发现了一个 NASTY 错误。

我们在每个 windows 版本下的标准代码需要:(ms)

时间 CreateFile/SetFilePointer 1 WriteFile 0 CloseHandle 0

在 Windows 2016 上,我们得到了:

时间 CreateFile/SetFilePointer 1 WriteFile 0 CloseHandle 275

而且时间随着文件的大小而增长,这很荒谬。

经过大量调查(我们首先发现“CloseHandle”是罪魁祸首......)我们发现在 windows2016 下,MS 在关闭函数中附加了一个“钩子”,触发“Windows Defender”扫描所有文件并阻止返回直到完成。 (换句话说,扫描是同步的,即纯粹的疯狂)。

当我们在“Defender”中为我们的文件添加排除项时,一切正常。 我认为这是一个BAD设计,没有防病毒软件会阻止正常的文件活动INSIDE程序空间扫描文件。 (MS可以这样做,因为他们有能力这样做。)

【讨论】:

    猜你喜欢
    • 2014-10-26
    • 2020-02-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-15
    • 2021-09-03
    • 2016-09-28
    • 2020-02-08
    • 2012-07-17
    相关资源
    最近更新 更多