最佳缓冲区大小确定

3
我猜这是一个性能计算问题。我正在用C语言编写一个程序,它会产生大量的输出,远超过RAM可以完全存储的量。我打算将输出直接写入stdout,因此它可能只是显示在屏幕上,也可能被重定向到文件中。我的问题是如何选择一种最佳的缓冲区大小,以存储在RAM中的数据?
输出数据本身并不特别重要,所以我们假设它是生成大量随机整数的数据。
我打算创建两个线程:一个线程负责生成数据并将其写入缓冲区,另一个线程则将该缓冲区写入stdout。这样,我就可以在上一个缓冲区仍在被写入stdout时开始生成下一个输出缓冲区。
需要明确的是,我的问题并不是关于如何使用malloc()pthread_create()等函数。我的问题纯粹是关于如何选择一个字节数(512、1024、1048576)作为最佳缓冲区大小,从而获得最佳性能?
理想情况下,我希望找到一种方法,在运行时动态地选择最佳缓冲区大小,以便我的程序可以适应任何硬件条件。我已经尝试搜索了解答案,虽然我找到了一些关于缓冲区大小的线程,但没有找到与这个问题特别相关的内容。因此,我想要发布这个问题,希望能够得到不同的观点,并提出比我个人更好的解决方案。
5个回答

6
混合设计和优化是浪费时间的大忌,这被认为是顶级规范错误之一。它很可能会损坏您的设计,并没有真正实现优化。
让程序先运行起来,如果有性能问题的迹象,那么就对其进行分析并考虑分析真正导致问题的部分。
我认为这尤其适用于像多线程应用程序这样复杂的架构优化。多线程单个图像是您永远不希望做的事情:它是不可能测试的,容易出现无法重现的错误,在不同的执行环境中将以不同的方式失败,还有其他问题。但是,对于某些程序,多线程并行执行是必需的功能或是获得必要性能的一种方法。它得到了广泛支持,本质上有时是一种必要的恶。
如果没有确凿证据表明像您的程序这样的程序需要它,那么最初的设计中就不需要它。
几乎任何其他并行方法(消息传递?)都将更容易实现和调试,并且在您的操作系统的I/O系统中已经有了很多这样的东西。

1
将内容写入标准输出可能会非常缓慢,特别是当未重定向到文件时。优化缓冲区大小所获得的任何性能提升可能都不会被注意到。 - Agent_L
抱歉,在问题中我应该说清楚。我有一个可以运行的程序。但目前它只是顺序执行。它产生一个输出,将其写入 stdout,然后循环。这就是为什么我现在考虑如何提高性能。 - greydamian
啊哈。那么,把缓冲区放在下游怎么样?实际上,只需使用已安装的 dd(1) 程序:$ a.out | dd bs=128k - DigitalRoss
酷,我没有想到那个。不过我不确定它会对性能产生什么影响?!我的程序仍然只有一个线程,有时会进行计算和生成输出,有时会写入 'stdout'。不过我一定会试试的,谢谢。 - greydamian
通过管道或本地磁盘进行的I/O速度很快。但对于终端来说,情况并非如此,因为字体渲染这些天变得非常复杂。但我怀疑你真正关心的只是程序在人类阅读时不会阻塞,因此下游管道连接的缓冲区应该能解决你的问题。(如果我理解正确的话。) - DigitalRoss
我可以问一下你所说的“当人类在阅读时阻塞”是什么意思吗?我一直以为,当程序输出到屏幕时,它会尽可能快地输出,只受字体渲染等不可避免的因素的限制。这是正确的吗?还是终端/ shell 为了用户的利益引入了额外的(可避免的)延迟? - greydamian

1
简短回答:测量它。
长篇回答:根据我的经验,这取决于许多难以预测的因素。另一方面,在开始之前,您不必做出承诺。只需实现通用解决方案,完成后进行几次性能测试,并选择具有最佳结果的设置。分析器可以帮助您集中精力处理程序中的性能关键部分。
从我所见,那些产生最快代码的人通常首先尝试最简单、直接的方法。他们比普通程序员做得更好的是,他们有很好的技巧编写良好的性能测试,这绝非易事。
没有经验,很容易陷入某些陷阱,例如忽略缓存效果,或者(也许在您的应用程序中?!)低估IO操作的成本。在最坏的情况下,您最终会挤压程序中根本不对整体性能做出贡献的部分。
回到您最初的问题:
在您描述的场景中(一个CPU限制的生产者和一个IO限制的消费者),很可能其中一个将成为瓶颈(除非生产者生成数据的速率变化很大)。根据哪个更快,整个情况会发生根本性的变化。

首先假设,IO限制是您的瓶颈(无论是写入stdout还是文件)。这会带来什么后果?

优化算法以生成数据将不会提高性能,相反,您必须最大化写入性能。然而,我认为写入性能不会非常依赖于缓冲区大小(除非缓冲区太小)。

在另一种情况下,如果生产者是限制因素,则情况将反转。在这里,您必须对生成代码进行分析并提高算法速度,可能还要改进读取器和写入器线程之间的数据通信。然而,缓冲区大小仍然不重要,因为大部分时间缓冲区都是空的。

当然,情况可能比我描述的更复杂。但是,除非您确实确定自己不处于极端情况之一,否则我不会投资于调整缓冲区大小。只需保持可配置即可,您应该没问题。我认为以后将其重新适配到其他硬件环境不应该成为问题。


你的意思是只是试错法:在代码中调整缓冲区大小,然后多次运行吗?! - greydamian
基本上是的。我知道听起来不够系统,但经验表明你通常会做出实践中并不成立的先入为主的假设。因此,从简单的开始,采用基准驱动方法来解决问题通常会带来良好的结果。 - Philipp Claßen
好的,我想我可以使用Valgrind。我只是希望能够将我的程序泛化,使其在任何机器上都能良好运行(不一定完美),而不是针对特定的机器进行调整。 - greydamian
@greydamian 我理解你的意思,但我认为首先在特定机器上进行基准测试以后不会对你造成问题。除非应用程序类型如此苛刻,以至于您必须使用架构特性来产生足够的解决方案。例如,在开发并发数据结构库时。在这种情况下,您确实必须创建要支持的体系结构模型,并专门为该模型设计。 - Philipp Claßen
感谢您对原始答案的补充。我最初的想法是stdout会成为我的程序瓶颈,因此我假设我能做到的最好的就是让stdout保持忙碌状态!如果这个假设是正确的,我希望有人能指导我以编程方式确定stdout的吞吐量,并确定保持其繁忙所需的最小缓冲区大小,或者试错(通过分析)仍然是这样做的最佳方法? - greydamian

1

我个人认为你在浪费时间。

首先,运行time ./myprog > /dev/null

现在,使用time dd if=/dev/zero of=myfile.data bs=1k count=12M

dd是一个非常简单的程序,它会很快地写入文件。但是写入几个GB仍然需要一点时间。(在我的机器上,12G大约需要4分钟 - 可能不是世界上最快的磁盘 - 相同大小的文件到/dev/null大约需要5秒)。

您可以尝试在bs=x count=y中使用一些不同的数字,其中组合与测试运行的程序输出大小相同。但我发现只有当你制作非常大的块时,它实际上需要更长的时间(每次写入1MB - 可能是因为操作系统需要在写入数据之前复制1MB,然后将其写出,然后复制下一个1MB,而使用较小的块(我测试了1k和4k),复制数据需要更少的时间,并且在我们写入它之前,实际上有更少的“磁盘旋转不做任何事情”)。

比较这两个时间和您程序的运行时间。使用 dd 写文件所需的时间是否比您的程序写文件所需的时间短得多?如果没有太大差异,则查看使用您的程序写入 /dev/null 所需的时间 - 这是否解释了部分或全部差异?

谢谢你的回答。你肯定可以认为我在浪费时间,因为可以说性能提升本身不值得投入时间和精力去研究。但是我更多地是出于个人兴趣、提高自己的知识水平和对编程的热爱而进行这项研究。不过,我一定会比较一下 dd 的性能。 - greydamian

0

大多数现代操作系统都非常擅长将磁盘用作 RAM 的后备存储。我建议您将启发式算法留给操作系统,只需请求所需的内存量,直到遇到性能瓶颈。


我想我应该在我的问题中提供一些数字,对此感到抱歉。目前,我正在运行这个软件的Linux系统上有4GB的RAM和8GB的交换空间,但程序很容易产生超过12GB的输出。因此,我认为像“malloc(1670616516608)”这样的东西显然是愚蠢的。 - greydamian
1
我猜测您会定期或在填满缓冲区时输出生成的数据?在这种情况下,缓冲区的大小实际上取决于数据生成的速度,而不是数据量。如果数据生成的速度与stdout的吞吐量相比非常高,我建议在文件上进行mmap并使用该内存。实际的缓冲区大小将根据您选择的任何IO策略(mmap vs write)尝试不同大小来决定。 - aagdbl
我的第一反应是,我程序的整体性能将受到我向 stdout 写入的速度的限制。因此,我所能做的最好的事情就是让 stdout 保持繁忙状态,这将决定我的缓冲区大小! - greydamian

0

不需要使用缓冲,操作系统会自动在必要时将页面交换到磁盘上,您无需编程。如果您不需要保存数据,最简单的方法就是将其留在RAM中,否则在生成数据后最好保存它,因为这对磁盘I/O更加有利。


感谢您的意见。当然,在可能的情况下,您是正确的。正如我在Darth Chatri的帖子中所评论的那样,我想我真的应该在我的问题中包含一些数字。我在Linux机器上运行这个程序,有4GB的RAM和8GB的交换空间,而我的程序可以轻松产生超过12GB的输出。因此,像malloc(1670616516608)这样的调用只会让人觉得傻。不幸的是,这就是为什么我不能生成所有的数据,并将其存储在一个单独的缓冲区中,然后输出该数据的原因。 - greydamian
在这种情况下,我会尽可能地将缓冲区设得更大,减去一些保留值(4GB-1.5GB),因此为2.5GB或甚至3GB(取决于其他应用程序的内存使用情况)。然后,每当缓冲区已满并且仍在写入stdout时,我仍然没有看到另一个选项,只能等待缓冲区再次为空。如果不等待缓冲区为空,那么当有空闲时就会不断向缓冲区中添加一个字节。但除非您的输出不是连续流,否则拥有缓冲区完全没有用处。缓冲区对于非连续数据流非常有用。 - 0xJarno
我希望找到一个答案,将最佳缓冲区大小与平均文件大小联系起来。在1990年代,我提出了32256字节(2 ^ 15-1磁盘扇区)作为最佳值,但现在似乎很慢,就像它导致了很多抖动。没有什么可以替代使用自己的典型数据设置自己的测试并得出数字。同时,使用任何看起来方便的东西,它可能会起作用。 - Alan Corey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接