【问题标题】:printf slows down my programprintf 减慢了我的程序
【发布时间】:2010-12-22 09:28:22
【问题描述】:

我有一个小 C 程序来计算哈希(用于哈希表)。我希望代码看起来很干净,但有一些与它无关的东西困扰着我。

我可以在大约 0.2-0.3 秒内轻松生成大约一百万个哈希(以 /usr/bin/time 为基准)。但是,当我在 for 循环中对它们进行 printf() 处理时,程序会减慢到大约 5 秒。

  1. 这是为什么呢?
  2. 如何让它更快? mmapp() 可能是标准输出?
  3. stdlibc 在这方面是如何设计的,如何改进?
  4. 内核如何更好地支持它?需要如何修改才能使本地“文件”(套接字、管道等)的吞吐量真正快速?

我期待着有趣而详细的回复。谢谢。

PS:这是一个编译器构建工具集,所以不要害羞地进入细节。虽然这与问题本身无关,但我只想指出我感兴趣的细节。

附录

我正在为解决方案和解释寻找更多程序化方法。确实,管道可以完成这项工作,但我无法控制“用户”的工作。

当然,我现在正在进行测试,“普通用户”不会这样做。但这并没有改变一个简单的 printf() 会减慢进程的事实,这是我试图找到最佳编程解决方案的问题。


附录 - 惊人的结果

参考时间是 TTY 内的普通 printf() 调用,大约需要 4 分 20 秒。

在 /dev/pts(例如 Konsole)下进行测试可将输出加速到大约 5 秒。

在我的测试代码中使用 setbuffer() 到 16384 的大小大约需要相同的时间,对于 8192 几乎相同:大约 6 秒。

setbuffer() 显然在使用时没有任何效果:它需要相同的时间(在 TTY 上大约 4 分钟,在 PTS 上大约 5 秒)。

令人惊讶的是,如果我在 TTY1 上开始测试,然后切换到另一个 TTY,它确实需要与在 PTS 上相同:大约5 秒。

结论:内核做了一些与可访问性和用户友好性有关的事情。呵呵!

通常,无论您是在 TTY 处于活动状态时盯着它看,还是切换到另一个 TTY,它都应该同样慢。


教训:运行输出密集型程序时,切换到另一个 TTY!

【问题讨论】:

  • 如果你将输出重定向到/dev/null,那么你的程序有多快?
  • @ammoQ:与重定向到任何常规文件时一样快:大约 0.5 秒。
  • 这不是一件“简单”的事情。 I/O 通常比直接 CPU 计算和总线操作慢几个数量级,实现它应该不会那么令人惊讶。
  • 令人惊讶的是,如果您在进程执行并显示某些内容的同时查看 TTY,则执行需要 4 分钟。如果不看 TTY,则需要 5 秒。
  • Flavius:那是因为在显示 TTY 时,每一行都需要向上滚动整个屏幕。屏幕上的每个字符单元都映射到屏幕缓冲区中的特定位置 - 因此移动字符意味着在屏幕缓冲区中移动字节。在 80 列控制台上,这意味着向上移动 24 行基本上是 memmove 几乎 2k - 为您输出的每一行完成。

标签: c performance linux-kernel stdout glibc


【解决方案1】:

如果您在控制台上使用 printf(),它通常会非常慢。我不知道为什么,但我相信它不会返回,直到控制台以图形方式显示输出的字符串。此外,您不能 mmap() 到标准输出。

写入文件应该快得多(但仍然比计算哈希慢几个数量级,所有 I/O 都很慢)。

【讨论】:

    【解决方案2】:

    您可以尝试将 shell 中的输出从控制台重定向到文件。使用它,可以在几秒钟内创建千兆字节大小的日志。

    【讨论】:

      【解决方案3】:
      1. I/O 总是比较慢 直接计算。该系统有 等待更多组件 可用以便使用它们。它 然后必须等待响应 在它可以继续之前。反过来 如果它只是计算,那么它是 只有真正在之间移动数据 RAM 和 CPU 寄存器。

      2. 我没有对此进行测试,但是将散列附加到字符串上,然后在末尾打印字符串可能会更快。尽管如果您使用的是 C 而不是 C++,这可能会很痛苦!

      恐怕 3 和 4 超出了我的范围。

      【讨论】:

        【解决方案4】:

        您可以将字符串存储在缓冲区中,并在缓冲区已满时或定期将它们输出到文件(或控制台)。

        如果输出到控制台,滚动通常是一个杀手。

        【讨论】:

        • +1,尤其适用于滚动。想象一下滚动中涉及的所有位图复制和位图复制......
        • 您的回复让我在干净的 TTY 和 Konsole 的托管 PTS 下测试了该程序。结果:Konsole 加快了速度!从 TTY 运行需要 4 分 20 秒(我认为这应该作为测试的真正参考),从 PTY 运行需要 5 秒。
        • 另一个用于滚动的 +1。只需在 GNU 屏幕中运行一些聊天程序(然后将其分离)就会大大加快速度!
        【解决方案5】:
        1. 为什么不按需创建字符串,而不是在构建时创建?一秒钟输出40屏数据是没有意义的,你怎么可能读到呢?为什么不根据需要创建输出并只显示最后一个屏幕,然后根据需要用户滚动???

        2. 为什么不使用 sprintf 打印到字符串,然后在内存中构建所有结果的串联字符串并在最后打印?

        3. 通过切换到 sprintf 可以清楚地看到格式转换花费了多少时间以及将结果显示到控制台并适当更改代码所花费的时间。

        4. 根据定义,控制台输出很慢,创建散列只需要处理几个字节的内存。控制台输出需要经过操作系统的许多层,一旦它最终到达可能是 9600 波特设备的显示驱动程序,就会有处理线程/进程锁定等的代码!或大型位图显示,滚动屏幕等简单功能可能涉及操作兆字节的内存。

        【讨论】:

        • 关于 (4):我意识到,但是如果我是操作系统编写者,是否可以将输出从一个位置复制到另一个位置/进程?如果是的话,在您看来,我将如何做才能加快速度?
        • 在过去的美好时光里,游戏程序员习惯于直接寻址输出设备,例如将字符实际写入显示内存 - 今天即使他们大部分使用库来与硬件对话,所以他们可以独立于设备并利用硬件加速。今天很少值得绕过这些层。
        【解决方案6】:

        由于 I/O 总是比 CPU 计算慢得多,因此您可以先将所有值存储在尽可能快的 I/O 中。所以如果你有足够的内存就使用,如果没有就使用文件,但它比内存慢得多。

        现在可以在之后或由另一个线程并行打印出这些值。所以计算线程可能不需要等到 printf 返回。

        【讨论】:

          【解决方案7】:

          无缓冲输出很慢。

          默认情况下stdout 是完全缓冲的,但是当附加到终端时,stdout 要么是无缓冲的,要么是行缓冲的。

          尝试使用setvbuf()stdout 开启缓冲,如下所示:

          char buffer[8192];
          
          setvbuf(stdout, buffer, _IOFBF, sizeof(buffer));
          

          【讨论】:

          • 哦,printf() 默认写入标准输出。我不会干扰 printf() 的工作方式。
          【解决方案8】:

          我猜终端类型正在使用一些缓冲输出操作,所以当你执行 printf 时,它不会在分微秒内输出,它存储在终端子系统的缓冲存储器中。

          这可能会受到其他可能导致速度变慢的因素的影响,也许除了您的程序之外还有一个内存密集型操作正在其上运行。简而言之,可能同时发生的事情太多了,分页、交换、另一个进程的大量 i/o、使用的内存配置、可能是内存升级等等。

          最好将字符串连接起来直到达到某个限制,然后在达到某个限制时立即将其全部写出。甚至使用 pthreads 来执行所需的进程。

          已编辑: 至于2,3,这超出了我的范围。对于 4,我不熟悉 Sun,但确实知道并弄乱了 Solaris, 可能有一个内核选项可以使用虚拟 tty.. 我承认这已经有一段时间了使用内核配置并重新编译它。因此,我的记忆可能不是很好,请根据选项了解一下。

          用户@主机:/usr/src/linux $ make; make menuconfig **OR kconfig if from X**

          这将启动内核菜单,深入了解设备子树下的视频设置部分..

          已编辑: 但是通过将文件添加到 proc 文件系统(如果确实存在这样的东西)或可能将开关传递到内核中,您可以对内核进行调整,就像这样(这是富有想象力的,并不意味着它确实存在),fastio

          希望这会有所帮助, 最好的祝福, 汤姆。

          【讨论】:

          • 感谢您的回答。这是一台linux机器,你可以在问题的标签中看到。
          • @Flavius:哎呀,对于 Sun 和 solaris 位,我很抱歉,我确定我不久前在编辑答案时看到了它。一定是在SO上与其他线程混淆了......道歉
          【解决方案9】:

          我很久以前就发现了 using this technique 一些应该很明显的东西。 不仅 I/O 很慢,尤其是对控制台而言,而且格式化十进制数字也不快。如果您可以将二进制数字放入大缓冲区,然后将它们写入文件,您会发现速度要快得多。

          此外,谁会阅读它们?如果没有人需要阅读它们,那么以人类可读的格式将它们全部打印出来是没有意义的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-27
            • 2012-03-17
            • 2021-04-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-15
            • 1970-01-01
            相关资源
            最近更新 更多