【问题标题】:What is more efficient, reading word by word from file or reading a line at a time and splitting the string using C ?什么更有效,从文件中逐字读取或一次读取一行并使用 C 拆分字符串?
【发布时间】:2013-01-05 06:17:09
【问题描述】:

我想用 C 语言开发一个应用程序,我需要从磁盘上的文件中逐字检查。有人告诉我,从文件中读取一行然后将其拆分为单词会更有效,因为需要更少的文件访问。是真的吗?

【问题讨论】:

  • 为什么不尝试两种方式,看看哪种方式实际上更快?
  • 我认为重要的考虑因素是,换行符是否具有特殊含义,或者它们就像任何空格一样?如果它们像任何空格,那么至少在主循环中,您应该阅读单词,忽略行。如果你使readWord函数或其他东西,那么你仍然可以读取和缓冲行,但如果行在主循环中没有特殊含义,请不要处理主循环中的行。

标签: c file processing-efficiency


【解决方案1】:

如果您知道您将需要整个文件,那么您可以尽可能大块地读取它(在极端情况下,您将一次性将整个文件内存映射到内存中) .你是对的,这是因为需要更少的文件访问。

但是,如果您的程序不慢,那么请以使其开发速度最快且最无错误的方式编写它。早期优化是一种严重的罪过。

【讨论】:

  • 应用程序的 alpha 版本已通过逐行读取创建。
  • 如果 alpha 版本有效,请不要费心去修改它。如果 alpha 版本有问题,请考虑改用scanf()
【解决方案2】:

不是真的,假设您将使用 scanf() 并且您对“单词”的定义与 scanf() 视为单词的定义相匹配。

标准 I/O 库将缓冲实际的磁盘读取,读取一行或一个单词在磁盘访问方面的 I/O 成本基本相同。如果您要使用 fread() 读取文件的大块,您可能会获得一些好处 — 以复杂性为代价。

但对于阅读文字,scanf() 和保护性字符串格式说明符(如 %99s(如果您的数组为 char word[100];)可能会正常工作并且可能更易于编码。

如果你对单词的定义比scanf()支持的定义更复杂,那么读行和拆分可能会更容易。

【讨论】:

    【解决方案3】:

    就拆分而言,在性能方面没有区别。您在一种情况下使用空格进行拆分,在另一种情况下使用换行符。

    但是,在 word 的情况下,它会影响您需要分配缓冲区 M 次,而在 line 的情况下,它将是 N 次,其中 M>N。因此,如果您采用分词方法,请先尝试计算总内存需求,分配那么多块(这样您最终不会得到碎片化的 M 个块),然后从该块中获取 M 个缓冲区。请注意,相同的方法可以应用于行拆分,但差异会不太明显。

    【讨论】:

    • +1:关于你对这些单词所做的其他事情的影响,而不是仅仅阅读它们!
    • @hyde,不明白你的评论。您的意思是说连续的 malloc 会自动导致从连续的块中分配内存,而这些缓冲区最终会被 scanf 和 likes 填充?这对于文件 io afaik 来说非常聪明。
    【解决方案4】:

    这是正确的,您应该将它们读入缓冲区,然后拆分为您定义为“单词”的任何内容。 唯一不正确的情况是,如果您可以让fscanf() 正确抓取您认为是单词的内容(值得怀疑)。

    【讨论】:

      【解决方案5】:

      主要的性能瓶颈可能是:

      • 对 stdio 文件 I/O 函数的任何调用。调用越少,开销就越少。
      • 动态内存分配。应该尽量少做。最终,对 malloc 的大量调用会导致堆分段。

      因此归结为一个经典的编程考虑因素:您可以获得快速的执行时间,或者您可以获得较低的内存使用率。您不能两者兼得,但您可以找到一些在执行时间和内存消耗方面都最有效的合适的中间立场。

      在一种极端情况下,可以通过将整个文件作为一个大块读取并将其上传到动态内存来获得最快的执行速度。或者另一个极端,您可以逐字节读取它并在读取时对其进行评估,这可能会使程序变慢但根本不会使用动态内存。

      您需要具备各种特定于 CPU 和特定于操作系统的功能的基本知识,才能最有效地优化代码。对齐、缓存内存布局、底层 API 函数调用的有效性等问题都很重要。

      为什么不尝试几种不同的方法并对其进行基准测试?

      【讨论】:

      • Stdio 文件 IO 函数并不是特别慢。他们被缓冲了。 getc() 甚至可以是宏!使用像open()read() 等系统调用,它们会被传递给内核并且它们很慢,这就是为什么通常应该首选使用stdio FILE* 函数的原因,除非你可以证明编写、测试和维护自己的缓冲是合理的代码。
      • @Hyde 这取决于您与什么进行比较。 Windows 程序员可能不会觉得它们很慢,而实时系统程序员会发现它们效率极低。但当然,这一切都归结为它们下面的任何 OS API。您对操作使用的操作系统做出假设,但他们没有发布此类详细信息,因此我们无法判断。据我们所知,他的系统可能甚至没有操作系统,尽管这似乎很有可能。
      • 如果实时系统程序员发现它们效率低下,那么我会说这是他正在使用的C库的错......因为好的库代码不会比他自己编写的更糟糕。
      • @Hyde stdio.h 效率低下的原因是这些函数带有大量开销任务,它们必须支持以符合标准。
      • @hyde 例如,假设我们只对打印一个整数感兴趣并使用 printf。首先,我们的参数通过晦涩的“默认参数提升”被咀嚼,可能使它们超出必要的范围。然后 printf 将不得不实现晦涩的 va_list 来整理变量参数列表。然后它必须实现对浮点数、字符串、字符、大整数类型、指针等的支持。然后对所有这些类型进行详细格式化。加上对八进制和十六进制的支持。
      【解决方案6】:

      实际上并没有回答你的确切问题(单词与行),但如果你需要同时在内存中的所有单词,那么最有效的方法是:

      1. 确定文件大小
      2. 为整个文件分配缓冲区加上一个字节
      3. 将整个文件读取到缓冲区,并将'\0' 放入额外的字节。
      4. 绕过它并计算它有多少字
      5. 分配char*(指向字的指针)或int(指向缓冲区的索引)索引数组,大小与字数匹配
      6. 对缓冲区进行第二次传递,并将单词首字母的地址或索引存储到索引数组中,并用'\0'(字符串结束标记)覆盖缓冲区中的其他字节。

      如果您有足够的内存,那么假设单词数的最坏情况可能会稍微快一些:(filesize+1) / 2(一个字母单词,中间有一个空格,文件中的字节数为奇数)。还采用带有索引数组的 Java ArrayList 或 Qt QVector 方法,并在字数超过当前容量时使用realloc() 将其大小加倍,将非常有效(由于加倍=指数增长,重新分配不会发生多次)。

      【讨论】:

        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 2011-04-12
        • 1970-01-01
        • 1970-01-01
        • 2016-04-30
        • 2019-11-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多