C中最快的文件读取

时间:2010-06-08 23:35:38

标签: c file performance

现在我正在使用fread()来读取文件,但在其他语言中,fread()效率很低,我被告知。这在C中是一样的吗?如果是这样,如何更快地完成文件读取?

8 个答案:

答案 0 :(得分:31)

真的没关系。

如果您正在从实际硬盘中读取数据,那么速度会很慢。硬盘就是你的瓶颈,就是这样。

现在,如果你对你的read / fread / what的调用很愚蠢,并说fread() - 一次一个字节,那么是的,它会变慢,因为fread的开销( )将超过从磁盘读取的开销。

如果您调用read / fread / whatever并请求相当一部分数据。这将取决于你正在做什么:有时所有想要/需要的是4个字节(得到一个uint32),但有时你可以读大块(4 KiB,64 KiB等等.RAM很便宜,去寻找重要的东西。)

如果您正在进行小读取,一些较高级别的调用(如fread())将通过缓冲背后的数据来实际帮助您。如果你正在进行大量读取,它可能没有用,但是从fread切换到读取可能不会产生那么大的改进,因为你在磁盘速度方面存在瓶颈。

简而言之:如果可以,请在阅读时申请自由金额,并尽量减少您所写的内容。对于大量,2的幂往往比其他任何东西更友好,但当然,它是操作系统,硬件和天气依赖。

所以,让我们看看这是否会带来任何差异:

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#define BUFFER_SIZE (1 * 1024 * 1024)
#define ITERATIONS (10 * 1024)

double now()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec / 1000000.;
}

int main()
{
    unsigned char buffer[BUFFER_SIZE]; // 1 MiB buffer

    double end_time;
    double total_time;
    int i, x, y;
    double start_time = now();

#ifdef USE_FREAD
    FILE *fp;
    fp = fopen("/dev/zero", "rb");
    for(i = 0; i < ITERATIONS; ++i)
    {
        fread(buffer, BUFFER_SIZE, 1, fp);
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += buffer[x];
        }
    }
    fclose(fp);
#elif USE_MMAP
    unsigned char *mmdata;
    int fd = open("/dev/zero", O_RDONLY);
    for(i = 0; i < ITERATIONS; ++i)
    {
        mmdata = mmap(NULL, BUFFER_SIZE, PROT_READ, MAP_PRIVATE, fd, i * BUFFER_SIZE);
        // But if we don't touch it, it won't be read...
        // I happen to know I have 4 KiB pages, YMMV
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += mmdata[x];
        }
        munmap(mmdata, BUFFER_SIZE);
    }
    close(fd);
#else
    int fd;
    fd = open("/dev/zero", O_RDONLY);
    for(i = 0; i < ITERATIONS; ++i)
    {
        read(fd, buffer, BUFFER_SIZE);
        for(x = 0; x < BUFFER_SIZE; x += 1024)
        {
            y += buffer[x];
        }
    }
    close(fd);

#endif

    end_time = now();
    total_time = end_time - start_time;

    printf("It took %f seconds to read 10 GiB. That's %f MiB/s.\n", total_time, ITERATIONS / total_time);

    return 0;
}

...产率:

$ gcc -o reading reading.c
$ ./reading ; ./reading ; ./reading 
It took 1.141995 seconds to read 10 GiB. That's 8966.764671 MiB/s.
It took 1.131412 seconds to read 10 GiB. That's 9050.637376 MiB/s.
It took 1.132440 seconds to read 10 GiB. That's 9042.420953 MiB/s.
$ gcc -o reading reading.c -DUSE_FREAD
$ ./reading ; ./reading ; ./reading 
It took 1.134837 seconds to read 10 GiB. That's 9023.322991 MiB/s.
It took 1.128971 seconds to read 10 GiB. That's 9070.207522 MiB/s.
It took 1.136845 seconds to read 10 GiB. That's 9007.383586 MiB/s.
$ gcc -o reading reading.c -DUSE_MMAP
$ ./reading ; ./reading ; ./reading 
It took 2.037207 seconds to read 10 GiB. That's 5026.489386 MiB/s.
It took 2.037060 seconds to read 10 GiB. That's 5026.852369 MiB/s.
It took 2.031698 seconds to read 10 GiB. That's 5040.119180 MiB/s.

......或没有明显的区别。 (恐惧有时会赢,有时会读)

注意:慢mmap令人惊讶。这可能是因为我要求它为我分配缓冲区。 (我不确定提供指针的要求......)

非常简短:不要过早优化。让它运行,使它正确,快速,顺序。


回到大众需求,我在一个真实的文件上运行测试。 (Ubuntu 10.04 32位桌面安装CD ISO的前675 MiB)结果如下:

# Using fread()
It took 31.363983 seconds to read 675 MiB. That's 21.521501 MiB/s.
It took 31.486195 seconds to read 675 MiB. That's 21.437967 MiB/s.
It took 31.509051 seconds to read 675 MiB. That's 21.422416 MiB/s.
It took 31.853389 seconds to read 675 MiB. That's 21.190838 MiB/s.
# Using read()
It took 33.052984 seconds to read 675 MiB. That's 20.421757 MiB/s.
It took 31.319416 seconds to read 675 MiB. That's 21.552126 MiB/s.
It took 39.453453 seconds to read 675 MiB. That's 17.108769 MiB/s.
It took 32.619912 seconds to read 675 MiB. That's 20.692882 MiB/s.
# Using mmap()
It took 31.897643 seconds to read 675 MiB. That's 21.161438 MiB/s.
It took 36.753138 seconds to read 675 MiB. That's 18.365779 MiB/s.
It took 36.175385 seconds to read 675 MiB. That's 18.659097 MiB/s.
It took 31.841998 seconds to read 675 MiB. That's 21.198419 MiB/s.

...和一个非常无聊的程序员以后,我们已经读取了CD ISO off磁盘。 12次。在每次测试之前,磁盘缓存被清除,并且在每次测试期间,有足够的RAM,大约相同的RAM,可以在RAM中自由地保存CD ISO两次。

感兴趣的一个注意事项:我最初使用大型malloc()来填充内存,从而最大限度地减少磁盘缓存的影响。值得注意的是mmap在这里表现得非常糟糕。另外两个解决方案只是运行,mmap运行,由于我无法解释的原因,开始推动内存交换,这导致其性能下降。 (据我所知,该程序没有泄漏(源代码在上面) - 实际的“已用内存”在整个试验期间保持不变。)

read()发布总体上最快的时间,fread()发布的时间确实一致。然而,在测试期间,这可能是一些小的打嗝。总而言之,这三种方法大致相同。 (特别是freadread ...)

答案 1 :(得分:15)

如果您愿意超越C规范进入OS特定代码,通常认为内存映射是最有效的方式。

对于Posix,请查看mmap,对于Windows,请查看OpenFileMapping

答案 2 :(得分:7)

什么让你失望?

如果您需要尽可能快的文件读取(同时仍然可以很好地使用操作系统),请直接进入操作系统的呼叫,并确保您学习如何最有效地使用它们。

  1. 您的数据如何实际布局?例如,旋转驱动器可能会更快地读取存储在边缘的数据,并且您希望最小化或消除搜索时间。
  2. 您的数据是否经过预处理?您是否需要在从磁盘加载数据并使用它之间做些什么?
  3. 读取的最佳块大小是多少?(可能是扇区大小的一部分。检查操作系统文档。)
  4. 如果搜索时间有问题,请将您的数据重新排列在磁盘上(如果可以的话)并将其存储在较大的预处理文件中,而不是从此处加载小块。

    如果数据传输时间有问题,可以考虑压缩数据。

答案 3 :(得分:1)

我正在考虑read系统调用。

请记住,fread是'read'的包装。

另一方面,fread有一个内部缓冲区,所以'read'可能会更快,但我认为'fread'会更有效率。

答案 4 :(得分:0)

如果fread速度很慢,那是因为它添加到底层操作系统机制的附加层,以便从干扰特定程序使用fread的方式的文件中读取。换句话说,它很慢,因为你没有按照优化的方式使用它。

话虽如此,通过了解操作系统I / O功能如何工作并提供自己的抽象来更好地处理程序的特定I / O访问模式,可以更快地读取文件。大多数情况下,您可以使用内存映射文件来完成此操作。

但是,如果您正在运行正在运行的计算机的限制,则内存映射可能不够。此时,您需要弄清楚如何优化I / O代码。

答案 5 :(得分:0)

有些人在这里注意到的问题是,根据您的源,目标缓冲区大小等,您可以为该特定情况创建自定义处理程序,但还有其他情况,如块/字符设备,即/ dev / *其中标准规则适用或不适用,你的支持源可能是串行弹出字符而没有任何缓冲,如I2C总线,标准RS-232等。还有一些其他来源字符设备是内存可映射的大部分内存,就像nvidia的视频驱动程序字符设备(/ dev / nvidiactl)一样。

许多人在高性能应用程序中选择的另一个设计实现是异步而不是同步I / O来处理数据的读取方式。查看libaio和libaio的移植版本,它们为异步I / O提供预先打包的解决方案,并考虑在工作者和消费者线程之间使用读取共享内存(但请记住,如果你去,这将增加编程的复杂性这条路线)。异步I / O也是您无法通过标准OS系统调用获得的stdio开箱即用的东西。请注意,根据规范,有些读取是“可移植的”,但并非所有操作系统(例如FreeBSD)都支持POSIX STREAM(可选)。

您可以做的另一件事(取决于数据的可移植性)是调查压缩和/或转换为二进制格式,如数据库格式,即BDB,SQL等。某些数据库格式可以使用字节顺序跨机器移植转换功能。

通常,最好采用一组算法和方法,使用不同的方法运行性能测试,并评估最适合您的应用程序所服务的平均任务的算法。这将有助于您确定性能最佳的算法。

答案 6 :(得分:0)

也许看看perl是如何做到的。 Perl的I / O例程已经过优化,我收集的是,使用perl过滤器处理文本的速度是使用sed进行相同转换的速度的两倍。

显然perl非常复杂,而I / O只是它的一小部分。我从来没有看过它的来源所以我没有给你更好的指示而不是指向你here

答案 7 :(得分:0)

这不是最快的,但是它很不错而且很短。

{{1}}