【问题标题】:The speed of fscanf and sscanffscanf 和 sscanf 的速度
【发布时间】:2021-03-02 03:43:30
【问题描述】:

对于 C 作业,我应该将大文本文件中的单词分解并逐个处理。基本上,一个词是任何线性的字母序列。因为,这将是我的程序的瓶颈,我想让这个过程尽可能快。

我的想法是使用扫描函数格式说明符 ([a-zA-z]) 将文件中的单词扫描到字符串缓冲区中。如果缓冲区已满,我检查文件中是否有更多字母(基于文件指针所在的位置)。如果有,那么我会增加缓冲区大小并继续将更多字母复制到缓冲区中,直到遇到非字母。

问题在于我使用的是 fscanf 还是 sscanf(将整个文件复制到一个字符串中)。一个比另一个快还是我的想法有更好的替代方案?

【问题讨论】:

  • 文件中的数据是固定格式的?
  • @ameyCU 只是一个文本文件。不知道什么是固定格式?
  • 大部分时间会在从磁盘读取数据时丢失,而不是在处理信息(如果你真的想要,fgets() 或 fgetc() 更快,但不是你的瓶颈) .如果可用,请考虑使用 ramdisk 的可能性。
  • @Heto 如果我使用 fgets 或 fgetc,我将需要处理每个字符(检查它们是否是字母)。我假设 fscanf 或 sscanf 实现在给我字母字符串方面会更好。
  • 假设...但在您完成测量以证明这一点之前,您只是在猜测。使用fgets()然后拆分线路,或使用getc()(如果您坚持使用fgetc(),可能会有可测量的差异)的性能不太可能有显着差异。但磁盘 I/O 时间仍占主导地位,您不太可能发现差异。

标签: c scanf


【解决方案1】:

您的问题几乎是题外话,因为它需要基于意见的答案。

要知道一种方法与另一种方法相比的速度有多快,唯一的方法是同时尝试两种方法,并在真实数据上测量生成的可执行文件的性能。

凭借当今普通 PC 的计算能力,需要非常大的文件来衡量实际的性能差异。

所以继续实施您的想法。您似乎对潜在的性能瓶颈有很好的理解,将这些想法转化为实际的 C 代码。为这个问题提供 2 个不同但正确的程序以及性能分析应该会让你获得 A+。作为雇主,我在测试中重视这种方法。

PS:恕我直言,大部分时间都花在从文件系统获取数据上。如果文件大于可用内存,那应该是你的瓶颈。如果文件可以放入操作系统文件系统缓存中,那么后续基准测试应该会给您带来比第一个更好的性能...

如果允许您编写特定于系统的代码,请尝试使用 mmap 和简单的 for 循环并通过在映射的 char 数组上查找表进行显式测试。

【讨论】:

  • 实际上,历史表明,几年后,仅仅需要 10MiB 的 JSON 就可以证明 sscanf() 的一些 C 库实现相对于字符串的长度是二次方的,而另一些则是不是。 nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70news.ycombinator.com/item?id=26297612github.com/biojppm/rapidyaml/issues/40
  • @JdeBP:惊人的发现!在这个长字符串上迭代使用sscanf() 有点蹩脚,但是sscanf() 调用strlen() 来计算字符串的长度,因为与fscanf() 共享的公共例程需要一些缓冲区大小,效率低下且有风险。如果有足够的字节可用于执行所有必需的转换,我什至不确定sscanf() 输入字符串是否必须为空终止。 strtol() 绝对不会出现这种行为。您的解决方法并不完全正确,因为可以在连续调用 strlen() 之间修改字符串,从而更改实际长度。
  • @JdeBP: 准确地说,sscanf() 本身没有二次时间复杂度,但是对于字符串长度来说是不必要的线性时间复杂度,在重复使用解析长 JSON 时会产生二次时间复杂度字符串。
【解决方案2】:

正如 Heto 在 cmets 中指出的,这里的主要瓶颈可能是从磁盘读取文件,而不是您决定使用的 scanf 函数变体。

如果你真的想加快你的应用程序,你应该尝试构建一个管道。正如您现在描述的应用程序一样,您基本上分两个阶段工作:将文件读入缓冲区,并从缓冲区解析单词。

如果您决定将整个文件读入一个字符串,然后在该字符串上使用sscanf,则活动可能如下所示:

reading: ████████████████
parsing:                 ████████████████

如果你直接在文件上使用fscanf,你会得到一些不同的东西,因为你会不断地在读取和解析之间切换:

reading: █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
parsing:  █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █

在这两种情况下,您最终花费的时间大致相同。

但是,如果您可以异步执行文件 i/o,那么您可以将等待来自磁盘的数据的时间与用于计算的时间重叠。理想情况下,您最终会得到这样的结果:

reading: ████████████████
parsing:  ████████████████

我的图表可能不是那么准确(我们已经指出,解析应该比 i/o 花费更少的时间,因此两个条的长度实际上不应该相同)——但你应该得到大概的概念。如果您可以设置一个管道,从处理过程中异步读取数据,那么您可以通过重叠通信(从磁盘读取)和计算(解析)来获得很大的加速。

您可以使用POSIX asynchronous I/O (aio) 实现这样的异步管道,或者只使用两个线程进行简单的生产者/消费者设置(其中一个从文件中读取,另一个进行解析)。


老实说,除非您正在处理 大量 文本文件,否则您可能几乎无法衡量您可能选择的任何可能方法之间的速度差异......

这种流水线方法更适用于计算密集型(不仅仅是扫描字符)并且通信延迟较高(例如当数据来自网络而不是来自本地磁盘时)的情况。但是,探索不同的选项仍然是一个很好的练习。毕竟,无论如何,作业都是人为设计的——关键是要学习一些有用的东西,以后可能会在实际项目中使用,对吧?


另外,使用scanf 中的任何一个都可能比仅循环缓冲区以提取字符串[A-Za-z] 慢。这是因为,对于任何scanf 函数,代码首先需要解析您的格式字符串 以找出您要查找的内容,然后实际解析输入。有时编译器可以做一些聪明的事情——比如 gcc 通常如何将没有格式说明符的printf 改为puts——但我认为scanf 和朋友没有这样的优化,特别是如果你是使用 %[A-Za-z] 之类的特殊内容,而不是 %d 之类的标准格式说明符。

【讨论】:

  • 如果读取/解析按照您的建议交错,则整个异步想法将是相关的。在现代操作系统上并非如此。操作系统会提前为您读取。
  • @MSalters - 你能详细说明一下吗?我认为 aio 建议可能有点偏离(显然,当您的磁盘读/写被缓冲时,它并没有那么有帮助,我认为这可能是您所得到的)——但我会认为线程建议可能会加快速度。如果一个线程将 1GB 文件从磁盘读取到字符串缓冲区,而另一个线程正在处理字符串缓冲区,那么(假设有多个硬件线程)我认为这会更快。
  • 事实上,这会更慢。现代操作系统会在您实际请求它们之前发现读取模式并从磁盘读取字节,这些最终会出现在文件缓存中。因此,您的读取操作会立即完成。单独的读取线程只会产生线程同步开销。
猜你喜欢
  • 2014-04-15
  • 2014-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多