用C语言快速读取文件

嗯,我想知道是否比使用fscanf()更快地读取文件的方法

例如,假设我有这个文本

4 55 k 52 o 24 l 523 i 

首先,我想读取第一个数字,它给出了以下行数。

将这个数字称为N.

在N之后,我想要读取具有整数和字符的N行。 使用fscanf会是这样的

 fscanf(fin,"%d %c",&a,&c); 

您几乎不进行任何处理,因此瓶颈可能是文件系统吞吐量。 但是,如果确实如此,您应该先测量。 如果您不想使用分析器,则只需测量应用程序的运行时间即可。 输入文件的大小除以运行时间可用于检查您是否已达到文件系统吞吐量限制。

然后,如果您远离上述限制,您可能需要优化读取文件的方式。 最好使用fread()以较大的块读取它,然后使用sscanf()处理存储在内存中的缓冲区。

您也可以自己解析缓冲区,这比*scanf()更快。

[编辑]

特别是对于Drakosha:

 $ time ./main1 Good entries: 10000000 real 0m3.732s user 0m3.531s sys 0m0.109s $ time ./main2 Good entries: 10000000 real 0m0.605s user 0m0.496s sys 0m0.094s 

因此,优化版本可以达到~127MB / s,这可能是我的文件系统的瓶颈,也可能是OS将文件缓存在RAM中。 原始版本约为20MB / s。

使用80MB文件进行测试:

 10000000 1234 a 1234 a ... 

main1.c

 #include  int ok = 0; void processEntry(int a, char c) { if (a == 1234 && c == 'a') { ++ok; } } int main(int argc, char **argv) { FILE *f = fopen("data.txt", "r"); int total = 0; int a; char c; int i = 0; fscanf(f, "%d", &total); for (i = 0; i < total; ++i) { if (2 != fscanf(f, "%d %c", &a, &c)) { fclose(f); return 1; } processEntry(a, c); } fclose(f); printf("Good entries: %d\n", ok); return (ok == total) ? 0 : 1; } 

main2.c

 #include  #include  int ok = 0; void processEntry(int a, char c) { if (a == 1234 && c == 'a') { ++ok; } } int main(int argc, char **argv) { FILE *f = fopen("data.txt", "r"); int total = 0; int a; char c; int i = 0; char *numberPtr = NULL; char buf[2048]; size_t toProcess = sizeof(buf); int state = 0; int fileLength, lengthLeft; fseek(f, 0, SEEK_END); fileLength = ftell(f); fseek(f, 0, SEEK_SET); fscanf(f, "%d", &total); // read the first line lengthLeft = fileLength - ftell(f); // read other lines using FSM do { if (lengthLeft < sizeof(buf)) { fread(buf, lengthLeft, 1, f); toProcess = lengthLeft; } else { fread(buf, sizeof(buf), 1, f); toProcess = sizeof(buf); } lengthLeft -= toProcess; for (i = 0; i < toProcess; ++i) { switch (state) { case 0: if (isdigit(buf[i])) { state = 1; a = buf[i] - '0'; } break; case 1: if (isdigit(buf[i])) { a = a * 10 + buf[i] - '0'; } else { state = 2; } break; case 2: if (isalpha(buf[i])) { state = 0; c = buf[i]; processEntry(a, c); } break; } } } while (toProcess == sizeof(buf)); fclose(f); printf("Good entries: %d\n", ok); return (ok == total) ? 0 : 1; } 

您不太可能显着加快实际数据读取速度。 这里的大部分时间都花在将数据从磁盘传输到内存,这是不可避免的。

您可以通过用fgets替换fscanf调用然后手动解析字符串(使用strtol )来绕过fscanf必须执行的格式字符串解析来获得一点加速,但不要指望任何巨大的节省。

最后,通常不值得大量优化I / O操作,因为它们通常将主要用于将实际数据传输到硬件/外设或从硬件/外设传输实际数据所需的时间。

像往常一样,从分析开始,以确保这部分确实是一个瓶颈。 实际上,FileSystem缓存应该使您所做的小读取不是非常昂贵,但是将更大的文件读取到内存然后在内存上运行可能会(稍微)更快。 如果(我认为非常不可能)是您需要保存每个CPU周期,您可以编写自己的fscanf变体,因为您知道字符串的格式,并且您只需要支持一个变体。 但这种改进也会带来低收益,特别是在现代CPU上。

输入看起来像在各种编程竞赛中。 在这种情况下 – 优化算法,而不是读数。

fgets()或fgetc()更快,因为他们不需要将fscanf()的整个格式/变量参数列表芭蕾拖动到程序中。 但是,这两个函数中的任何一个都将为您提供手动字符转整数转换。 不过,整个程序会更快。

由于是系统调用,因此不太希望更快地读取文件。 但是有很多方法可以比使用专门代码的scanf更快地解析它。

结账readfread 。 当你练习编程竞赛时,你可以忽略关于磁盘IO的所有警告,因为文件可以在内存中,或者来自其他进程的管道,可以“即时”生成测试。

将您的测试放入/dev/shm (tmpfs的新解决方案)或制作测试生成器并管道它。

我在编程竞赛中发现,以atoi方式解析数字可以比scanf / fscanf提供更多的性能提升( atoi可能不存在,所以要准备好手工实现它 – 这很容易)。