【问题标题】:Reading a file faster in C在 C 中更快地读取文件
【发布时间】:2011-06-18 14:14:11
【问题描述】:

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

例如假设我有这个文本

4

55 k

52 o

24 l

523 i

首先我想读取第一个数字,它为我们提供了以下行数。

让这个数字被称为 N。

在 N 之后,我想读取 N 行,其中包含一个整数和一个字符。 使用fscanf 会是这样的

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

【问题讨论】:

  • 您的分析器是否告诉您fscanf 是您的性能问题的根源?
  • 这听起来有点像家庭作业。
  • 问题应该是更快地解析文件而不是读取。
  • 基本上我想在练习比赛中使用它。
  • @Sotiris 您是否尝试以 MB/s 为单位测量程序的读取速度?也许它接近您的硬盘最大读取速度。你肯定需要衡量和分析。

标签: c file scanf performance


【解决方案1】:

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

如果您远离上述限制,您可能需要优化读取文件的方式。使用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,这可能是我的文件系统的瓶颈,或者操作系统可能会将文件缓存在 RAM 中。原始版本约为 20MB/s。

使用 80MB 文件进行测试:

10000000

1234 a

1234 a
...

ma​​in1.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;
}

ma​​in2.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;
}

【讨论】:

  • 时差是多少?你测试的文件大小是多少?
  • @ssmir:为努力+1。您能否以相反的顺序进行测试,即 main2,在 main1 之后。我相信差异是因为 FS 缓存。更好的是在代码中测量IO的时间,这样剩下的时间都是计算。
  • @Drakosha 顺序无关紧要,我连续运行它们大约五次,并在它稳定后发布最终结果。当然,FS 缓存在这里起着重要作用,因为我在另一台带有 SATA 硬盘的 PC 上达到了 285 MB/s。如果我将读取文件的方式更改为普通 fgetc (codepad.org/r5gqC7S4),我会在运行其他两个版本的系统上获得 1.806 秒。
  • @ssmir:谢谢,我想知道读取是否会有所不同或您的解析实现。
  • @Drakosha 当然,两者都是。如果您逐字节读取文件,您的 C 库将不会提前读取它,因为它可能会阻塞,例如当您从标准输入读取时。所以每个fgetc() 至少需要一个系统调用。但是当你使用fread() 时,你会在一个系统调用中读取一大块。它要快得多。我的解析算法肯定很快。
【解决方案2】:

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

您可以通过将fscanf 调用替换为fgets 然后手动解析字符串(使用strtol)来绕过fscanf 必须执行的格式字符串解析,从而加快速度,但是不要指望节省大量资金。

最后,大量优化 I/O 操作通常是不值得的,因为它们通常会被将实际数据传输到/从硬件/外围设备所花费的时间支配。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      fgets() 或 fgetc() 更快,因为它们不需要将 fscanf() 的整个格式化/变量参数列表芭蕾拖到程序中。然而,这两个函数中的任何一个都会让您手动进行字符到整数的转换。尽管如此,整个程序会更快。

      【讨论】:

      • 如何逐字节读取文件比大块读取文件更快?
      • @Drakosha。很多。自然,它完全取决于 CPU、操作系统和编译器。
      • @ssmir 无论算法如何,从磁盘读取的实际时间都可能是恒定的。这完全取决于硬件和系统,原始帖子中都没有提到。如果不考虑硬件访问时间,我们只剩下算法开销可供使用。而且所有的 printf/scanf 函数都非常慢。
      • @Lundin:时间分布大概会是:disk > 99% fscanf
      • @Drakosha 不,您假设文件位于硬盘驱动器上,而不是闪存、RAM 或 EEPROM 中。没有人提到 PC 编程 :)
      【解决方案5】:

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

      【讨论】:

      • C 标准中没有任何内容表明 fscanf() 必须调用某些操作系统例程。在某些情况下,fscanf() 可能是硬件之上的唯一层。
      • @Lundin:你知道任何实际的系统是这样的吗?在 'FILE*' 级别拥有最低级别的 API 并同时进行读取和解析会很奇怪,如果它是在硬件级别完成的更是如此。但是,好吧,这并非不可能……只是糟糕的设计。
      • 我已经看到 fscanf() 等函数在几个微控制器上实现为闪存编程例程。我不觉得它奇怪或糟糕的设计,一台计算机很可能有一个文件系统但没有操作系统。一个应用示例是 mp3 播放器。
      • @Lundi:好的,有道理。我主要不同意,因为当您谈到“硬件”时,我并没有想到闪存程序,而是更多地想到了 ASIC。但是好的,在某种程度上,您无法修改此类嵌入式库中提供的内容。如果从文件中读取一些原始字节,我仍然会觉得很奇怪,如果普通读取不可用,我将不得不使用fscanf(f, "%c", &amp;c); 而不是使用read(fd, buf, n)fread(p, 1, n, f); 循环。但我敢打赌,你所说的系统上也可以使用 fread。
      【解决方案6】:

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

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

      我在编程竞赛中发现,以atoi 的方式解析数字可以大大提高 scanf/fscanf 的性能(atoi 可能不存在,所以准备好手动实现它 - 这很容易)。

      【讨论】:

      • P.S.请告诉我们从管道进程的 /dev/shm 读取数据时 read、fread、scanf、fscanf 之间的速度差异 :)。
      猜你喜欢
      • 2017-03-26
      • 2017-02-15
      • 2021-04-16
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 2019-08-29
      • 2016-02-21
      相关资源
      最近更新 更多