在C中更快地读取文件

时间:2011-01-31 13:29:24

标签: c file scanf performance

嗯,我想知道是否比使用fscanf()

更快地读取文件

例如,假设我有这个文本

4

55 k

52 o

24 l

523 i

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

将此号码称为N。

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

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

6 个答案:

答案 0 :(得分:3)

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

然后,如果您远离上述限制,您可能需要优化读取文件的方式。最好使用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 <stdio.h>

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 <stdio.h>
#include <stdlib.h>

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;
}

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

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

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