将HTTP响应写入临时的bytes.Buffer中。

huangapple go评论74阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2014年6月9日 20:37:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/24120466.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定