我当时正在开发一个简单的解析器,分析时发现瓶颈在...文件中!我提取了一个非常简单的测试,以便在读取大量数据时比较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毫秒),这可能是因为文件被缓存了。这就是为什么打开的文件无法使用该代码创建的原因。

为什么与fstream相比,仅使用FILE*读取二进制数据的 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);

并启动了探查器。似乎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

编辑4 由于某种原因,此问题被标记为重复。我想指出的是,我根本不使用printf,只使用std::cout来编写时间。 read部分中使用的文件是write部分的输出,并以其他名称复制以避免缓存

最佳答案

在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实现确实有些愚蠢-可能会将整个数据分小块,以某种方式或类似方式复制。我将尝试进一步查找。

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

编辑:

看来,现在在我的机器上,如果在写入后添加fclose(file),则在使用旧式旋转磁盘的系统上,fstreamFILE*花费的时间大致相同-在我的系统上,写入1GB大约需要13秒类型的驱动器,而不是SSD。

但是我可以使用以下代码更快地编写MUCH:
#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::fstreams这么慢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26095160/

10-11 22:13
查看更多