英文:
Writing HTTP responses to a temporary bytes.Buffer
问题
我一直在进行一些分析和基准测试,以优化将内容写入临时的bytes.Buffer
,以便捕获template.ExecuteTemplate
中的任何错误。
具体来说,我们将内容写入缓冲区,检查是否有错误,如果没有错误,则将其写入http.ResponseWriter
。然而,问题在于临时缓冲区有一定的请求开销:
- 关闭分析时,每秒约6.2k个请求 - 27.6k -> 21.4k,打开分析时,每秒约29k个请求 -> 24k个请求;
- 每个请求的延迟增加了9毫秒(40ms -> 49ms)。
当然,每秒21k个请求仍然是很多的请求,但是22%的性能损失也是相当大的影响。
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
// 确保模板存在于映射中。
tmpl, ok := templates[name]
if !ok {
return ErrTemplateDoesNotExist
}
// 创建一个缓冲区进行临时写入,并检查是否遇到任何错误。
buf := bytes.NewBuffer(make([]byte, 0, 10000))
err := tmpl.ExecuteTemplate(buf, "base", data)
if err != nil {
return err
}
// 设置头部并将缓冲区写入http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
return nil
}
10K的缓冲区大小是对大多数响应的典型最大页面大小的粗略估计,尽管我还没有测试超出这个大小的响应。响应大于缓冲区大小通常会导致性能下降20%。
是否有更好的方法在每个请求中写入临时缓冲区? 另一个开发者指出Go 1.3中即将推出的sync.Pool,但是我不确定如何开始使用它。
补充: 目前使用http://godoc.org/github.com/oxtoacart/bpool 可以达到每秒33k个请求,每个请求36ms的性能:
var bufpool *bpool.BufferPool
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
...
buf := bufpool.Get()
err := tmpl.ExecuteTemplate(buf, "base", data)
if err != nil {
return err
}
// 设置头部并将缓冲区写入http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
bufpool.Put(buf)
return nil
}
func init() {
bufpool = bpool.NewBufferPool(48)
}
英文:
I've been doing some profiling and benchmarking in order to optimise writing out to a temporary bytes.Buffer
to catch any errors from template.ExecuteTemplate
.
Specifically, we're writing to the buffer, checking for any errors, and if none, writing out to our http.ResponseWriter
. The problem, however, is that the temporary buffer has a request overhead that's somewhat noticeable:
- About 6.2k req/s - 27.6k -> 21.4k with profiling on, and 29k -> 24k with it off;
- A 9ms (40ms -> 49ms) increase in per request latency.
Of course, 21k req/s is still a lot of requests, but a 22% perf. hit is also a fairly large impact.
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
// Ensure the template exists in the map.
tmpl, ok := templates[name]
if !ok {
return ErrTemplateDoesNotExist
}
// Create a buffer to temporarily write to and check if any errors were encountered.
buf := bytes.NewBuffer(make([]byte, 0, 10000))
err := tmpl.ExecuteTemplate(buf, "base", data)
if err != nil {
return err
}
// Set the header and write the buffer to the http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
return nil
}
The 10K buffer size is a rough estimation of the typical max page size of most of my responses, although I've yet to test this beyond a small handful of pages just yet. A response larger than the buffer size typically results in another 20% hit to performance.
Is there a better way to write to a temporary buffer in every request? Another gopher pointed out the upcoming sync.Pool in Go 1.3, but I'm not sure where to start when it comes to writing that out.
Added: using http://godoc.org/github.com/oxtoacart/bpool at the moment yields 33k req/s at 36ms per request:
var bufpool *bpool.BufferPool
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
...
buf := bufpool.Get()
err := tmpl.ExecuteTemplate(buf, "base", data)
if err != nil {
return err
}
// Set the header and write the buffer to the http.ResponseWriter
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
bufpool.Put(buf)
return nil
}
func init() {
bufpool = bpool.NewBufferPool(48)
}
答案1
得分: 3
只需使用一个可用的池来汇集你的缓冲区,而不是使用标准库中的池。这个看起来可以工作(在godoc中搜索一些其他的替代方案):
http://godoc.org/github.com/oxtoacart/bpool
通过减少垃圾收集器的压力,你可能还会看到吞吐量的增加,无论大小如何。
英文:
[copied from comments as an answer]
Just pool your buffers using an available pool not from the standard library. This one looks like it will work (search godoc a bit for a few other alternatives):
http://godoc.org/github.com/oxtoacart/bpool
Yyou should probably also see an increase in throughput regardless of size, just by reducing the garbage collector pressure.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论