[英]Tomcat Servlet performance: StringBuilder vs. direct write
這是供Tomcat /網絡專家使用的工具。 我會對其進行基准測試/ Wireshark,但這要求很高,也許有人會立即知道答案。
比較這兩種生成servlet輸出的方法,從用戶的角度來看,這是最快的方法:
直接寫入servlet輸出流:
for(int i = 0; i <10000; i ++){servletOutputStream.write(“ a”); / *有點延遲* /}
創建一個緩沖區並一圈寫入
for(int i = 0; i <10000; i ++){stringbuffer.append(“ a”); } servletOutputStream.write(stringBuffer.toString())
我可以想象方法1的優點是響應可以快速開始發送內容,而方法2中的發送則稍后開始。
另一方面,方法1可以生成更多/較小的TCP數據包,進而需要更長的時間才能完全傳輸?
問候
PS:請不要告訴我這是過早的優化。 在這種情況下,我有一個同時提供toString
和write(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中的這種緩沖到底是如何工作的 ,使用它的最佳策略是什么?
(而且我並不擔心或期望獲得更大的速度提升。我只是想知道事情的運行方式。)
我希望ServletOutputStream
實際上是
org.apache.tomcat.core.BufferedServletOutputStream
(顧名思義)這是一個緩沖的流。 這將意味着最好直接將字符寫入流中,而不是將它們組裝在StringBuffer
或StringBuilder
並寫入結果。 直接書寫將避免字符的至少一個副本。
如果事實證明您的ServletOutputStream
尚未被緩沖,則可以將其包裝在BufferedOutputStream
,您將獲得相同的結果。
假設現在您正在談論流。 (刷新StringBuffer
沒有意義。)
這些緩沖區什么時候刷新?
當它們已滿時,當您在流上調用flush
或關閉流時。
...而使用它的最佳策略是什么?
通常,寫入數據,完成后關閉文件。 除非有充分的理由,否則不要顯式刷新。 如果要傳遞普通的HTTP響應,則很少。 (刷新可能會導致網絡堆棧通過發送更多網絡數據包來傳輸相同數量的信息。這可能會影響整體網絡吞吐量。)
對於Servlet框架,我記得Servlet規范說,當請求/響應處理完成時, ServletOutputStream
將自動刷新並關閉。 如果您沒有包裝ServletOutputStream
,那么甚至不需要關閉流。 (雖然沒有害處。)
毫無疑問,由於多種原因,直接寫入輸出流會更快:
StringBuilder
可能會變得非常大,占用大量堆空間 StringBuilder
會定期重新分配其空間,從而導致創建新對象,在各處復制數據等 然而
我認為您的分析沒有考慮到一個非常重要的因素:檢測和從錯誤中恢復。
如果您的servlet正在執行一個半復雜的過程,則該過程隨時可能失敗。 如果在渲染一半輸出后失敗,則您將無法執行以下任何操作:
因此,即使手動緩沖的方法(基於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
編碼,客戶端在下載完所有數據之前永遠不知道會有多少數據。
將servletOutputStream
包裝在BufferedOutputStream
(除非已經存在),而您不必擔心類似這樣的愚蠢的事情。
我肯定會用第一個。 Servlet輸出流已緩沖,因此您不必擔心發送速度太快。 另外,您每次都會在第二個字符串中分配一個新字符串,這可能會導致GC超時開銷。 使用第一個,並在循環后調用flush。
它已經被緩沖,在某些情況下,它已寫入ByteArrayOutputStream
以便Tomcat可以在Content-Length標頭之前。 不用擔心
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.