在读取大数据时,是否有比fscanf更快的方法

时间:2015-10-22 13:58:26

标签: c optimization file-io

我正在努力应对大规模的结构化数据。我有一个包含姓名和门号的文件。我使用fscanf读取名称和数字然后我使用fprintf将它们存储在较小的文件中。

while ( fscanf(file, "%s %d", &people[i].name, &people[i].doorNum) > 0 ) {      
      ...
}

人是结构数组

typedef struct {                                        
   char* name;
   int doorNum;
}person;

我想读的文件是15 GB。我的目标是阅读它们并分成1GB文件。它完美无缺,但需要10分钟以上。如何改进这些读写数据流程?

3 个答案:

答案 0 :(得分:1)

你没有通过“分裂”来告诉我们你的意思。

可能是将字段作为字符串和整数读取是没用的(可能是单个字符串或两个单独的字符串就足够了)。

使用内置的匹配模式知识编写自己的扫描功能,这肯定会更有效率。即使把你自己的转换写成整数也应该更好。

答案 1 :(得分:0)

<img>有很多功能,不太可能同时使用,这使得速度变慢。我建议您使用fscanf()编写自己的函数代码。由于您的功能只有一个特定任务,因此应该更快。

答案 2 :(得分:0)

所有文件都包含二进制数据。有些格式是有效的,有些则不是。

例如;要存储数字0x1234,您可以将其存储为2字节序列0x34, 0x12,以便可以使用少量简单/快速操作(例如value = buffer[pos] | (buffer[pos+1] << 8);)重建它。这样会相对有效。

可替换地;你可以将它存储为5字节序列0x34, 0x36, 0x36, 0x40, 0x00,其中每个字节代表一个字符串中的ASCII字符(末尾有一个零终结符);那么你可以使用像这样的昂贵的循环扫描字节并将它们从十进制转换为整数:

    while( (c = buffer[pos++]) != 0) {
        if( (c < '0') || (c > '9') ) {
             // Error condition(!)
        }
        value = value * 10 + c - '0';
     }

然后你可以通过将它包装在“方便”(例如fscanf())中使代码变得更糟,其中代码必须扫描格式字符串,以确定它需要做那样昂贵的循环。

基本上;如果您关心性能和/或效率(包括文件大小),则需要停止使用“纯文本”并设计适合数据的文件格式;特别是当你看到巨大的15 GB文件时。

编辑:添加以下所有内容!

如果你坚持使用“纯文本”,那么你可以通过自己进行更多的解析来获得更多的性能(例如使用atoi()之类的函数等)。除此之外的下一步是使用您自己的(更专业的)例程而不是像atoi()这样的函数。

除此之外的下一步是使用确定性有限状态机。一般的想法可能会像:

    switch( state | buffer[pos++] ) {
        case START_OF_LINE | 'A':
        case START_OF_LINE | 'B':
        case START_OF_LINE | 'C':
            string_start = pos - 1;
            string_length = 1;
            state = GETTING_NAME;
            break;
        case GETTING_NAME | 'A':
        case GETTING_NAME | 'B':
        case GETTING_NAME | 'C':
            string_length++;
            break;
        case GETTING_NAME | ' ':
            number = 0;
            state = GETTING_NUMBER;
            break;
        case GETTING_NUMBER | '0':
            number = number * 10;
            break;
        case GETTING_NUMBER | '1':
            number = number * 10 + 1;
            break;
        case GETTING_NUMBER | '2':
            number = number * 10 + 2;
            break;
        case GETTING_NUMBER | '\n':
            create_structure(string, string_length, number);
            line++;
            state = START_OF_LINE;
            break;
        default:
            // Invalid character
            printf("Parse error at line %u!\n", line);
            break;
    }

希望编译器能够获得最终的巨大switch()并将其优化为快速跳转表。当然,手工构建这样的东西既痛苦又容易出错;并且您可能能够找到一个“解析器生成器”来为您完成(基于规则)。

除此之外的下一步是多线程。例如,您可以让一个线程扫描文件,搜索'\n'个字符,当它找到一个时,它将该线路移到工作线程(工作线程可以使用上述任何方法进行解析)这条线)。通过这种方式,您可以将多个CPU并行解析。

除了所有这些之外;您希望在解析数据时从磁盘加载数据。例如;当您处理第一个MiB数据时,您希望并行加载第二个MiB数据;并且您不想加载1 MiB然后解析1 MiB,然后加载下一个MiB然后解析下一个MiB等。为此,您需要使用类似(例如)POSIX异步IO函数;或者(在支持预取的64位操作系统上)内存映射文件。