28

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

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

  1. 为什么是这样?
  2. 如何让它更快?mmapp()ing 标准输出可能吗?
  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!

4

9 回答 9

34

无缓冲输出非常慢。

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

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

char buffer[8192];

setvbuf(stdout, buffer, _IOFBF, sizeof(buffer));
于 2009-12-02T12:28:21.470 回答
15

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

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

于 2009-12-02T12:06:37.077 回答
9

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

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

于 2009-12-02T12:03:03.530 回答
7

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

于 2009-12-02T12:05:54.773 回答
7
  1. 与直接计算相比,I/O 总是很慢。系统必须等待更多组件可用才能使用它们。然后它必须等待响应才能继续。相反,如果它只是计算,那么它只是在 RAM 和 CPU 寄存器之间真正移动数据。

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

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

于 2009-12-02T12:06:16.380 回答
4
  1. 为什么不按需创建字符串,而不是在构建时创建?一秒钟输出40屏数据是没有意义的,你怎么可能读到呢?为什么不根据需要创建输出并只显示最后一个屏幕,然后根据需要用户滚动?

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

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

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

于 2009-12-02T12:17:27.730 回答
4

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

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

于 2009-12-02T12:24:28.170 回答
4

很久以前,我发现使用这种技术的东西应该是显而易见的。不仅 I/O 很慢,尤其是对控制台而言,而且格式化十进制数字也不快。如果您可以将二进制数字放入大缓冲区,然后将它们写入文件,您会发现它要快得多。

此外,谁来阅读它们?如果没有人需要阅读所有这些,那么以人类可读的格式打印它们是没有意义的。

于 2009-12-02T13:26:18.260 回答
2

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

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

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

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

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

这将启动内核菜单,深入查看设备子树下的视频设置部分。

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

希望这会有所帮助,最好的问候,汤姆。

于 2009-12-02T12:40:43.063 回答