【问题标题】:Tomcat Servlet performance: StringBuilder vs. direct writeTomcat Servlet 性能:StringBuilder 与直接写入
【发布时间】:2016-01-27 04:03:09
【问题描述】:

这是给tomcat/网络专家的一篇。我会对其进行基准测试/wireshark,但这要求很高,也许有人知道答案。

比较这两种生成 servlet 输出的方法,从用户的角度来看,哪一种是最快的:

  1. 直接写入 servlet 输出流:

    for(int i=0;i

  2. 创建缓冲区并一次性写入

    for(int i=0;i

我可以想象方法 1 的优点是响应可以快速开始发送内容,而在方法 2 中发送稍后开始。

另一方面,方法 1 可以生成更多/更小的 TCP 数据包,而这些数据包又可能需要更长的时间才能完全传输?

问候

PS:请不要告诉我这是过早的优化。在手头的情况下,我有一个提供toStringwrite(Appendable a) 方法的对象。我只需要在这里选择使用哪一个。此外,从理论的角度和关于 servlet 的一般设计,我发现这非常有趣。

编辑:感谢大家的回答。但似乎我的问题不清楚或过于简单化了我的例子。

我根本不担心没有缓冲。我知道在发送队列中必须至少在一个地方进行缓冲。可能它在多个地方(Java、OS、硬件)。我认为我真正的问题是:这些缓冲区何时刷新?

为了更清楚起见,假设我们的 MTU 为 1000,并且发送连续数据包是由硬件的缓冲区空中断触发的。那么在第一种情况下,它可能看起来像:

. packet( "a" ) //triggered by the first write( "a" ),
. packet( "aaaaaaa" ) // triggered by buffer-empty, sending the amount of "a"s which have been written in the meantime
. packet( "aaaa" ) // and so on
. packet( "aaaaaaaaaaa" )
...x1000 // or so in this example

而对于第二种情况,发送开始时已经有 10000 个字节可用,因此结果将是:

. packet( "aaaa....a(x1000)" )
. packet( "aaaa....a(x1000)" )
...x10

即使对于较小的数据大小(小于 MTU,比如说 100 个“a”)并且创建输出更快,它也可以发送结果可能看起来像:

. packet( "a" ) // first write
. packet( "aaaa...a(x99) ) // all remaining data available when buffer-empty interrupt.

当然,如果缓冲区的工作方式不同,这一切都会完全不同。例如。如果他们正在等待更多数据发送或等待刷新发送任何东西......(但这反过来也会在某些方面减慢发送速度)

所以这是我不知道的:tomcat 中的这种缓冲究竟是如何工作的,使用它的最佳策略是什么?

(而且我并不担心或期待更大的速度提升。我只是想知道事情是如何运作的。)

【问题讨论】:

    标签: java tomcat servlets networking


    【解决方案1】:

    我希望ServletOutputStream 实际上是一个实例

        org.apache.tomcat.core.BufferedServletOutputStream 
    

    (顾名思义)是一个缓冲流。这意味着最好将字符直接写入流,而不是将它们组合成StringBufferStringBuilder 并写入结果。直接写会避免至少一个字符的副本。

    如果你的ServletOutputStream 没有被缓冲,那么你可以将它包裹在BufferedOutputStream 中,你会得到同样的结果。


    现在假设您现在正在谈论流。 (刷新StringBuffer 没有任何意义。)

    这些缓冲区何时刷新?

    当它们已满时,当您在流上调用 flush 时,或者当流关闭时。

    ...使用它的最佳策略是什么?

    一般来说,写入数据,完成后关闭文件。不要显式刷新,除非有充分的理由这样做。如果您提供普通的 HTTP 响应,则很少有。 (刷新可能会导致网络堆栈通过发送更多网络数据包来传输相同数量的信息。这可能会影响整体网络吞吐量。)

    在 servlet 框架的情况下,我记得 Servlet 规范说,当请求/响应处理完成时,ServletOutputStream 将自动刷新和关闭。如果您没有包装 ServletOutputStream,您甚至不需要 关闭流。 (不过也没什么坏处。)

    【讨论】:

    • 嗨斯蒂芬,请看我的编辑。我对这些缓冲区如何/何时刷新更感兴趣。我编辑了我的问题,以更清楚地说明我在追求什么。不过谢谢你的回答!
    【解决方案2】:

    毫无疑问,直接写入输出流会更快,原因有很多:

    1. 输出缓冲区是固定的
    2. 输出缓冲区将在满时自动刷新(我认为何时发生这种情况并不重要,所以不要担心)
    3. 输出缓冲区将被重新使用
    4. 您的StringBuilder 可能会变得非常大,占用大量堆空间
    5. 您的StringBuilder 将不时重新分配其空间,从而导致创建新对象、将数据复制到各处等
    6. 所有这些内存活动都会产生 GC 必须处理的“垃圾”

    但是

    我认为您的分析没有考虑一个非常重要的因素:检测和从错误中恢复。

    如果您的 servlet 正在执行一个半复杂的过程,它可能随时失败。如果在渲染一半输出后失败,您将无法执行以下任何操作:

    1. 发出“错误”HTTP 状态代码(例如 500 服务器错误)
    2. 将用户重定向到另一个页面(错误页面?)
    3. 在屏幕上显示漂亮的错误消息,而不会破坏/中断页面

    因此,即使手动缓冲方法(基于 StringBuilder)效率较低,我相信它为您处理错误提供了很大的灵活性。

    这更像是一个宗教论点,但您会发现许多 Web 应用程序程序员会说您的 servlet 根本不应该产生任何输出,并且应该将生成响应的任务委托给另一个更适合的组件到任务(例如 JSP、Velocity、FreeMarker 等)。

    但是,如果您在编写 servlet 时着眼于原始速度,那么请务必:直接写入输出流。它将在微基准测试和负载下的整体速度方面为您提供最佳性能。

    编辑 2016-01-26

    这些缓冲区何时被刷新?

    servlet 规范不保证 ServletOutputStream 是否被缓冲,但不使用缓冲区将是一个实际错误:一次发送一个字符的 TCP 数据包肯定会很糟糕性能。

    如果您绝对需要确保响应被缓冲,您必须使用自己的BufferedOutputStream,因为 servlet 容器可以随时更改其实现,并且如上所述,不能保证为您缓冲响应.

    Tomcat 中的缓冲究竟是如何工作的?

    目前在 Tomcat 中实现的缓冲就像标准 JDK 类中的缓冲一样工作:当缓冲区填满时,它会被刷新到较低的流中,然后在调用后剩余的字节数保留在缓冲区中。

    如果您在流上手动调用flush,您将强制使用Transfer-Encoding: chunked,这意味着需要通过网络发送额外的数据,因为没有Content-Length(除非您手动设置开始填充缓冲区之前的一个)。如果可以避免分块编码,则可以为自己节省一些网络流量。此外,如果客户端知道响应的Content-Length,他们可以在下载资源时显示准确的进度条。使用chunked 编码,客户端在全部下载之前永远不知道有多少数据。

    【讨论】:

      【解决方案3】:

      将您的 servletOutputStream 包裹在 BufferedOutputStream 中(除非已经如此),您无需担心此类愚蠢的事情。

      【讨论】:

        【解决方案4】:

        我肯定会使用第一个。 servlet 输出流是缓冲的,因此您不必担心发送太快。此外,您每次都为第二个字符串分配一个新字符串,这可能会导致 GC 开销超时。使用第一个并在循环后调用flush。

        【讨论】:

          【解决方案5】:

          它已经被缓冲了,在某些情况下它被写入ByteArrayOutputStream,这样Tomcat 就可以在Content-Length 头之前添加。不用担心。

          【讨论】:

            猜你喜欢
            • 2014-06-12
            • 2013-02-27
            • 2019-01-06
            • 2015-12-23
            • 1970-01-01
            • 2012-01-11
            • 1970-01-01
            • 2022-07-23
            • 1970-01-01
            相关资源
            最近更新 更多