C中最快的文件读取

Jay*_*Jay 18 c performance file

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

Tha*_*tos 32

这真的不应该.

如果您正在从实际的硬盘读取数据,那将会很慢.硬盘是你的瓶颈,就是这样.

现在,如果你对你的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;
}
Run Code Online (Sandbox Code Playgroud)

...收益率:

$ 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.
Run Code Online (Sandbox Code Playgroud)

......或没有明显的差异.(恐惧有时会赢,有时会读)

注意:缓慢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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 看到有多少人继续鼓吹过早优化,好像他们对提问者手头的任务有更好的专业知识,这很有趣。我们提出这些问题是为了寻找真正优化我们代码的方法,而不是听到 Knuth 哲学的重申。 (4认同)
  • @Thantos:您不能使用 /dev/zero 进行基准测试。使用真实文件进行基准测试——像 /dev/zero 这样的元文件不会匹配 IO 系统的实际使用,因为它们不读取任何数据。 (3认同)
  • 9533.226368 MiB/s ...不知何故,我认为您的代码是错误的。这比通常运行的 RAM 快,更不用说硬盘驱动器了。 (2认同)
  • 这是因为mmap实际上会读取它,否则,**它不会交换**中的数据< - 这就是mmap通常更快的原因.通常你不需要文件的所有字节.内存映射对写入也有帮助,因为它允许操作系统推迟写入,直到写入本身有效. (2认同)
  • @mcabral:这只会给CPU带来压力,而不是I/O系统.@ lh3:CPU开销与IO系统无关.更重要的是系统如何利用现实世界的I/O设备,如硬盘驱动器.能够将多个读取条带化的解决方案将占用更多的CPU时间,但是当您将它们指向真实世界的I/O负载(即硬盘驱动器)时,它们的性能会更好.如果@Thanatos使用真实文件作为他的基准测试我会将我的downvote更改为upvote. (2认同)

R S*_*hko 15

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

对于Posix,请查看mmap并查看WindowsOpenFileMapping

  • 实际上,您需要CreateFileMapping和MapViewOfFile.OpenFileMapping仅用于打开现有的命名内存映射文件对象,例如用于共享内存.但+1表示内存映射周期. (7认同)

Mat*_*tis 8

什么在减慢你的速度?

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

  1. 您的数据如何实际布局?例如,旋转驱动器可能会更快地读取存储在边缘的数据,并且您希望最小化或消除搜索时间.
  2. 您的数据是否经过预处理?你需要在从磁盘加载和使用它之间做些什么吗?
  3. 阅读的最佳块大小是多少?(它可能是扇区大小的一部分.检查您的操作系统文档.)

如果搜索时间有问题,请将您的数据重新排列在磁盘上(如果可以的话)并将其存储在较大的预处理文件中,而不是从这里和那里加载小块.

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

  • 对于对此表示反对的人:我很想知道为什么。我的回答是错误的还是误导性的? (2认同)