K_T*_*K_T 24 c io binaryfiles fseek
读取大型二进制文件(2-3 GB)的每30个字节的最快方法是什么?我已经读过由于I/O缓冲区导致fseek存在性能问题,但我不想在每30个字节抓取之前将2-3 GB数据读入内存.
Cam*_*Cam 24
我建议你创建一个几千字节的缓冲区,从它每30个字节读取一次,用接下来的几千字节重新加载缓冲区,然后继续直到你到达eof.这样,读入内存的数据量就会受到限制,您也不必经常从文件中读取数据.你会发现你创建的缓冲区越大,它就越快.
编辑:实际上,如下所示,你可能想让你的缓冲区几百kb,而不是几千字节(就像我说的 - 更大的缓冲区=更快的文件读取).
Ste*_*sop 17
性能测试.如果您想自己使用它,请注意完整性检查(打印总计)仅在"step"划分BUFSZ时有效,并且MEGS足够小以至于您不读取文件的末尾.这是由于(a)懒惰,(b)希望不掩盖真实的代码.rand1.data是从/ dev/urandom复制的几GB dd.
#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);
}
Run Code Online (Sandbox Code Playgroud)
结果:
$ 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
Run Code Online (Sandbox Code Playgroud)
摘要:
我最初使用20MB的数据,当然适合缓存.我第一次读取它(使用32KB缓冲区)需要1.4s,将其带入缓存.第二次(使用32字节缓冲区)需要0.17秒.第三次(再次使用32KB缓冲区)需要0.03秒,这太接近我的计时器的粒度才有意义.fseek需要20多秒,即使数据已经在磁盘缓存中.
在这一点上,我将fseek从环中拉出来,以便其他两个可以继续:
$ 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
Run Code Online (Sandbox Code Playgroud)
1000MB的数据似乎也基本上被缓存了.32KB缓冲区比32字节缓冲区快6倍.但不同之处在于所有用户时间,而不是在磁盘I/O上花费的时间.现在,8000MB远远超过我的RAM,所以我可以避免缓存:
$ 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
Run Code Online (Sandbox Code Playgroud)
忽略这三个中的第一个,它从已经在RAM中的第一个1000MB文件中受益.
现在,32KB的版本在挂钟时间上稍微快一点(我不能再重新运行了,所以现在让我们忽略它),但是看看用户+系统时间的差异:20s vs. 82S.我认为我的操作系统的推测预读磁盘缓存已经保存了32字节缓冲区的培根:当32字节缓冲区正在缓慢重新填充时,操作系统正在加载接下来的几个磁盘扇区,即使没有人要求它们.如果没有这一点,我怀疑它会比32KB缓冲区慢一点(20%),在请求下一次读取之前,它在用户区中花费的时间更少.
故事的道德:标准I/O缓冲并没有在我的实现中削减它,fseek的表现是恶劣的,因为提问者说.当文件缓存在OS中时,缓冲区大小是一个大问题.当文件未缓存在操作系统中时,缓冲区大小与挂钟时间没有太大区别,但我的CPU比较繁忙.
incrediman使用读缓冲区的基本建议至关重要,因为fseek令人震惊.争论缓冲区应该是几KB还是几百KB在我的机器上很可能毫无意义,可能是因为操作系统已经完成了确保操作严格受I/O限制的工作.但我很确定这是由OS磁盘预读而不是标准的I/O缓冲,因为如果是后者那么fseek会比它更好.实际上,可能是标准I/O正在进行预读,但是fseek的过于简单的实现每次都会丢弃缓冲区.我没有查看实现(如果我这样做,我无法跨越边界进入操作系统和文件系统驱动程序).
Joh*_*ler 10
好吧,你可以读取一个字节,然后在循环中寻找29个字节.但IO子系统必须按扇区读取文件,这些扇区的大小通常为512字节,因此它仍将最终读取整个文件.
从长远来看,以块大小的倍数读取整个文件会更快,然后只需查看缓冲区.如果确保缓冲区大小是30的倍数,那么你将使你的生活变得更简单,如果它是512的倍数,你可以使文件子系统的生命更轻松.
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)
{
}
}
Run Code Online (Sandbox Code Playgroud)
这可能看起来效率低下,但它会比尝试读取30个字节的块更快.
顺便说说.如果您在Windows上运行,并且愿意特定于操作系统,那么您实际上无法击败内存映射文件的性能. 如何扫描磁盘上真正庞大的文件?
如果您愿意打破ANSI-C并使用特定于OS的调用,我建议使用内存映射文件.这是Posix版本(Windows有自己的OS特定调用):
#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);
Run Code Online (Sandbox Code Playgroud)