C 读取大型二进制文件每30字节的最快方法?
读取大型二进制文件(2-3 GB)的每30个字节的最快方法是什么?我已经了解到,由于I/O缓冲区的原因,fseek存在性能问题,但我也不想在抓取每30个字节之前将2-3 GB的数据读入内存。我建议您创建一个几千个字节的缓冲区,从中读取每30个字节,然后用接下来的几千个字节重新加载缓冲区,然后继续,直到到达eof。这样,读入内存的数据量是有限的,而且您也不必像以前那样频繁地从文件中读取数据。您会发现,创建的缓冲区越大,速度就越快 编辑:实际上,正如下面所建议的,您可能希望将缓冲区设置为几百kb,而不是几千字节(正如我所说的-更大的缓冲区=更快的文件读取)。嗯,您可以读取一个字节,然后在循环中查找29个字节。但是IO子系统必须按扇区读取文件,扇区的大小通常为512字节,因此它仍然会读取整个文件 从长远来看,只需将整个文件以步长的倍数分块读取,然后查看缓冲区,速度会更快。如果您确保缓冲区大小是30的倍数,那么您的生活就会简单一些;如果缓冲区大小是512的倍数,那么fileio子系统的生活就会轻松一些C 读取大型二进制文件每30字节的最快方法?,c,io,binaryfiles,fseek,C,Io,Binaryfiles,Fseek,读取大型二进制文件(2-3 GB)的每30个字节的最快方法是什么?我已经了解到,由于I/O缓冲区的原因,fseek存在性能问题,但我也不想在抓取每30个字节之前将2-3 GB的数据读入内存。我建议您创建一个几千个字节的缓冲区,从中读取每30个字节,然后用接下来的几千个字节重新加载缓冲区,然后继续,直到到达eof。这样,读入内存的数据量是有限的,而且您也不必像以前那样频繁地从文件中读取数据。您会发现,创建的缓冲区越大,速度就越快 编辑:实际上,正如下面所建议的,您可能希望将缓冲区设置为几百kb,而
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(
在POSIX系统上)将节省操作系统将数据从内核空间复制到用户空间的成本。这一节省可能是巨大的mmap
- 如下面的详细说明所示(感谢Steve Jessop的基准测试),如果您关心I/O性能,您应该从AT&T高级软件技术组下载Phong Vo。它比C的标准I/O库更安全、设计更好、速度更快。在经常使用
的程序上,速度会显著加快: 在一个简单的微型基准上,速度可提高七倍fseek
- 只要放松一下,使用
和fseek
,它们的设计和实现完全是为了解决您的问题fgetc
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
为30k)BUFSZ
- 使用
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