C 读取大型二进制文件每30字节的最快方法?

C 读取大型二进制文件每30字节的最快方法?,c,io,binaryfiles,fseek,C,Io,Binaryfiles,Fseek,读取大型二进制文件(2-3 GB)的每30个字节的最快方法是什么?我已经了解到,由于I/O缓冲区的原因,fseek存在性能问题,但我也不想在抓取每30个字节之前将2-3 GB的数据读入内存。我建议您创建一个几千个字节的缓冲区,从中读取每30个字节,然后用接下来的几千个字节重新加载缓冲区,然后继续,直到到达eof。这样,读入内存的数据量是有限的,而且您也不必像以前那样频繁地从文件中读取数据。您会发现,创建的缓冲区越大,速度就越快 编辑:实际上,正如下面所建议的,您可能希望将缓冲区设置为几百kb,而

读取大型二进制文件(2-3 GB)的每30个字节的最快方法是什么?我已经了解到,由于I/O缓冲区的原因,fseek存在性能问题,但我也不想在抓取每30个字节之前将2-3 GB的数据读入内存。

我建议您创建一个几千个字节的缓冲区,从中读取每30个字节,然后用接下来的几千个字节重新加载缓冲区,然后继续,直到到达eof。这样,读入内存的数据量是有限的,而且您也不必像以前那样频繁地从文件中读取数据。您会发现,创建的缓冲区越大,速度就越快

编辑:实际上,正如下面所建议的,您可能希望将缓冲区设置为几百kb,而不是几千字节(正如我所说的-更大的缓冲区=更快的文件读取)。

嗯,您可以读取一个字节,然后在循环中查找29个字节。但是IO子系统必须按扇区读取文件,扇区的大小通常为512字节,因此它仍然会读取整个文件

从长远来看,只需将整个文件以步长的倍数分块读取,然后查看缓冲区,速度会更快。如果您确保缓冲区大小是30的倍数,那么您的生活就会简单一些;如果缓冲区大小是512的倍数,那么fileio子系统的生活就会轻松一些

while (still more file to read)
{ 
   char buf[30 * 512];
   int cread = fread (buf, sizeof(buf), 1, fd);
   for (int ii = 0; ii < cread; ii += 30)
   {

   }
}
while(还有更多文件要读)
{ 
char-buf[30*512];
int cread=fread(buf,sizeof(buf),1,fd);
对于(int ii=0;ii
这看起来效率很低,但实际上比尝试读取30字节的块要快

顺便说一下。如果您在Windows上运行,并且愿意成为特定于操作系统的用户,那么内存映射文件的性能将是无与伦比的。

如果您愿意脱离ANSI-C并使用特定于操作系统的调用,我建议您使用内存映射文件。这是Posix版本(Windows有自己的操作系统特定调用):

#定义地图大小4096
int fd=打开(仅文件);
结构统计stbuf;
fstat(fd和stbuf);
char*addr=0;
关闭上次映射的偏移量=-1;
off_t idx=0;
而(idx<标准尺寸)
{
如果(上次映射的偏移量!=(idx/MAPSIZE))
{
如果(地址)
munmap(addr,MAPSIZE);
最后映射的偏移量=idx/MAPSIZE;
addr=mmmap(0,映射大小,保护读取,映射文件,fd,idx,最后映射的偏移量);
}
*(addr+(idx%MAPSIZE));
idx+=30;
}
munmap(addr,MAPSIZE);
关闭(fd);

你几乎肯定不需要担心它。运行时可以很好地缓冲为每个文件句柄读取的最后一个块。即使没有,操作系统也会为您缓存文件访问


这就是说,如果您一次读取一个块,就可以节省fseek和fread函数的调用开销。您一次读取的数据块越大,您节省的呼叫开销就越多——尽管其他成本显然已经超出了某一点。

缓冲I/O库的整个目的就是让您摆脱这些顾虑。如果你必须每隔30个字节读取一次,操作系统将读取整个文件,因为操作系统读取的是更大的数据块。以下是您的选项,从最高性能到最低性能:

  • 如果您有很大的地址空间(即,您在64位硬件上运行64位操作系统),那么使用内存映射IO(
    mmap
    在POSIX系统上)将节省操作系统将数据从内核空间复制到用户空间的成本。这一节省可能是巨大的

  • 如下面的详细说明所示(感谢Steve Jessop的基准测试),如果您关心I/O性能,您应该从AT&T高级软件技术组下载Phong Vo。它比C的标准I/O库更安全、设计更好、速度更快。在经常使用
    fseek
    的程序上,速度会显著加快: 在一个简单的微型基准上,速度可提高七倍

  • 只要放松一下,使用
    fseek
    fgetc
    ,它们的设计和实现完全是为了解决您的问题

如果你认真对待这个问题,你应该对所有三个备选方案进行测量。Steve Jessop和我展示了使用
fseek
要慢得多,如果使用GNU C库,
fseek
要慢得多。您应该测量
mmap
;它可能是最快的


附录:您希望查看文件系统,确保它能够快速从磁盘上释放2–3GB的容量。例如,XFS可能胜过ext2。当然,如果你坚持使用NTFS或HFS+,速度会很慢

令人震惊的结果刚刚出现
我在Linux上重复了Steve Jessop的测量。GNU C库在每个
fseek
上进行系统调用。除非POSIX出于某种原因需要它,否则它是疯狂的。我可以咀嚼一堆1和0,然后吐出一个比这更好的缓冲I/O库。无论如何,成本增加了大约20倍,其中大部分用于内核。如果您使用
fgetc
而不是
fread
读取单个字节,则在小型基准测试中可以节省约20%

有一个像样的I/O库,结果不那么令人震惊 我又做了一次实验,这次使用了Phong Vo的
sfio
库。读取200MB需要

  • 0.15s,不使用
    fseek
    BUFSZ
    为30k)
  • 使用
    fseek
重复测量表明,如果没有
fseek
,使用sfio仍然可以减少大约10%的运行时间,但是运行时间非常嘈杂(几乎所有的时间都花在操作系统上)

在这台计算机(笔记本电脑)上,我没有足够的可用磁盘空间来运行磁盘缓存中不适合的文件,但我愿意得出以下结论:

  • 使用合理的I/O库,
    fseek
    更昂贵,但不是更昂贵
    #define MAPSIZE 4096
    int fd = open(file, O_RDONLY);
    struct stat stbuf;
    fstat(fd, &stbuf);
    
    
    char *addr = 0;
    off_t last_mapped_offset = -1;
    off_t idx = 0;
    while (idx < stbuf.st_size)
    {
        if (last_mapped_offset != (idx / MAPSIZE))
        {
            if (addr)
                munmap(addr, MAPSIZE);
    
            last_mapped_offset = idx / MAPSIZE; 
    
            addr = mmmap(0, MAPSIZE, PROT_READ, MAP_FILE, fd, idx, last_mapped_offset);
        }
    
        *(addr + (idx % MAPSIZE));
    
        idx += 30;
    
    }
    
    munmap(addr, MAPSIZE);
    close(fd);
    
    #include <stdio.h>
    #include <stdlib.h>
    
    const long long size = 1024LL*1024*MEGS;
    const int step = 32;
    
    int main() {
        FILE *in = fopen("/cygdrive/c/rand1.data", "rb");
        int total = 0;
        #if SEEK
            long long i = 0;
            char buf[1];
            while (i < size) {
                fread(buf, 1, 1, in);
                total += (unsigned char) buf[0];
                fseek(in, step - 1, SEEK_CUR);
                i += step;
            }
        #endif
        #ifdef BUFSZ
            long long i = 0;
            char buf[BUFSZ];
            while (i < size) {
                fread(buf, BUFSZ, 1, in);
                i += BUFSZ;
                for (int j = 0; j < BUFSZ; j += step) 
                    total += (unsigned char) buf[j];
            }
        #endif
        printf("%d\n", total);
    }
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=20 && time ./buff2
    83595817
    
    real    0m1.391s
    user    0m0.030s
    sys     0m0.030s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32 -DMEGS=20 && time ./buff2
    83595817
    
    real    0m0.172s
    user    0m0.108s
    sys     0m0.046s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=20 && time ./buff2
    83595817
    
    real    0m0.031s
    user    0m0.030s
    sys     0m0.015s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32 -DMEGS=20 && time ./buff2
    83595817
    
    real    0m0.141s
    user    0m0.140s
    sys     0m0.015s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DSEEK -DMEGS=20 && time ./buff2
    83595817
    
    real    0m20.797s
    user    0m1.733s
    sys     0m9.140s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=1000 && time ./buff2
    -117681741
    
    real    0m33.437s
    user    0m0.749s
    sys     0m1.562s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32 -DMEGS=1000 && time ./buff2
    -117681741
    
    real    0m6.078s
    user    0m5.030s
    sys     0m0.484s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=1000 && time ./buff2
    -117681741
    
    real    0m1.141s
    user    0m0.280s
    sys     0m0.500s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32 -DMEGS=1000 && time ./buff2
    -117681741
    
    real    0m6.094s
    user    0m4.968s
    sys     0m0.640s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=1000 && time ./buff2
    -117681741
    
    real    0m1.140s
    user    0m0.171s
    sys     0m0.640s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=8000 && time ./buff2
    -938074821
    
    real    3m25.515s
    user    0m5.155s
    sys     0m12.640s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32 -DMEGS=8000 && time ./buff2
    -938074821
    
    real    3m59.015s
    user    1m11.061s
    sys     0m10.999s
    
    $ gcc -std=c99 buff2.c -obuff2 -O3 -DBUFSZ=32*1024 -DMEGS=8000 && time ./buff2
    -938074821
    
    real    3m42.423s
    user    0m5.577s
    sys     0m14.484s