【问题标题】:Possible alternatives to speed up reads from a text file in c?加快从 c 中读取文本文件的可能替代方法?
【发布时间】:2013-04-18 07:27:56
【问题描述】:

我正在开发一个机器学习应用程序,我的特征存储在巨大的文本文件中。目前我实现数据输入读取的方式,这是一种缓慢实用的方式。基本上,文本文件的每一行都代表一个稀疏格式的特征向量。例如,以下示例包含三个 index:value 风格的特征。

1:0.34 2:0.67 6:0.99 12:2.1 28:2.1
2:0.12 22:0.27 26:9.8 69:1.8
3:0.24 4:67.0 7:1.9 13:8.1 18:1.7 32:3.4

以下是我现在的阅读方式。因为我事先不知道特征字符串的长度,所以我只是读取了一个适当大的长度,它限制了每个字符串的长度。有一次,我从文件中读取了该行,我只是使用strtok_r 函数将字符串拆分为键值对,然后进一步处理它以存储为稀疏数组。任何关于如何加快速度的想法都非常感谢。

FILE *fp = fopen(feature_file, "r");

int fvec_length = 0;
char line[1000000];
size_t ln;
char *pair, *single, *brkt, *brkb;

SVECTOR **fvecs = (SVECTOR **)malloc(n_fvecs*sizeof(SVECTOR *));
if(!fvecs) die("Memory Error.");

int j = 0;

while( fgets(line,1000000,fp) ) {
    ln = strlen(line) - 1;
    if (line[ln] == '\n')
        line[ln] = '\0';

    fvec_length = 0;
    for(pair = strtok_r(line, " ", &brkt); pair; pair = strtok_r(NULL, " ", &brkt)){
        fvec_length++;
        words = (WORD *) realloc(words, fvec_length*sizeof(WORD));
        if(!words) die("Memory error.");
        j = 0;
        for (single = strtok_r(pair, ":", &brkb); single; single = strtok_r(NULL, ":", &brkb)){
            if(j == 0){
                words[fvec_length-1].wnum = atoi(single);
            }
            else{
                words[fvec_length-1].weight = atof(single); 
            }
            j++;
        }
    }   
    fvec_length++; 
    words = (WORD *) realloc(words, fvec_length*sizeof(WORD));
    if(!words) die("Memory error.");
    words[fvec_length-1].wnum = 0;
    words[fvec_length-1].weight = 0.0;

    fvecs[i] = create_svector(words,"",1);
    free(words);
    words = NULL;
}
fclose(fp);
return fvecs;

【问题讨论】:

  • 替换 ln = strlen(line) - 1; if (line[ln] == '\n') line[ln] = '\0';只需 if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0';
  • 你试过分析它吗?
  • if(j==0) 与所有磁盘 I/O 和 malloc 相比,保证几乎不需要任何时间
  • 您获得了什么性能 (MB/s) 以及您期望/需要什么性能?文件读取部分看起来不错,但我有点担心频繁的重新分配。以块为单位增加大小可能会提高性能(假设 realloc 是耗时的)。
  • 如果你知道每一行正好有 5 个元素,你可以很容易地使用fscanf() 直接解析成目标地址。仍然可以使用它或 sscanf 否则,但更棘手。另外,尝试减少重新分配的频率 - 例如首先为 100 个字分配空间,然后增加一个您喜欢的乘数(例如 1.1、1.5、2),以调整内存效率与可能调整大小的数量。 (如果您想要极致速度,请考虑对输入文件进行内存映射。)

标签: c file-io io


【解决方案1】:
  1. 您绝对应该减少内存分配的数量。经典的方法是在每次分配时将向量加倍,这样您就可以得到对数的分配调用次数,而不是线性的。

  2. 由于您的行模式似乎是恒定的,因此无需手动对其进行标记,在每个加载的行上使用单个 sscanf() 来直接扫描该行的文字。

  3. 您的行缓冲区似乎非常大,这可能会导致堆栈爆炸,使缓存局部性恶化。

【讨论】:

  • 谢谢!我尝试了你给出的 3 条建议。关于第一点,它确实将读取速度提高了大约 50%,这非常酷。 sscanf() 然而,由于某种奇怪的原因,读取速度似乎减慢了一个数量级,所以坚持使用strtok()。关于第三个建议,我改用getline()。不提供任何性能改进,但是,代码更整洁。如果您有任何其他建议,我们将不胜感激!
【解决方案2】:

可能当您调用 realloc 时,您正在执行系统调用。系统调用是一项昂贵的操作,涉及上下文交换以及从用户空间到内核空间的切换,反之亦然。

您似乎正在为您获得的每一对令牌进行 realloc 调用。这是很多电话。您不关心之前将 1MByte 分配给文件指向的缓冲区。为什么你对 word 指向的缓冲区这么保守?

【讨论】:

  • 谢谢,我将尝试您的建议,以便在realloc 上轻松一点。此外,您是否认为文件指向的 1MByte 缓冲区也可能会减慢速度?我应该寻找更聪明的方法吗?
【解决方案3】:

我发现在 Linux (Fedora) 上 realloc() 非常有效并且不会减慢速度,尤其是。在 Windows 上,由于内存的结构,它可能是灾难性的。

我对“长度未知的行”问题的解决方案是编写一个函数,该函数多次调用 fgets(),将结果连接起来,直到检测到换行符。该函数接受 &maxlinelength 作为参数,如果任何对 fgets() 的调用会导致连接的字符串超过 maxlinelength,则调整 maxlinelength。这样,新内存只会在找到最长的行之前重新分配。同样,如果 maxlinelength 已调整,您只需要为 WORD 重新分配()

【讨论】:

  • 大多数时候,处理realloc 调用的是标准库。它不会是 Linux 与 Windows,但可能是 glibc 与 Visual C 的库。如果您在 Windows 上尝试使用 gcc 和 glibc,您可能不会发现 Linux 的效率有任何差异。
  • 谢谢 - 我听说这两个操作系统的内存管理方式不同,但你可能是对的 - 无论如何我们离题了!
猜你喜欢
  • 2014-03-25
  • 2012-08-26
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
  • 2011-04-18
  • 1970-01-01
相关资源
最近更新 更多