【问题标题】:Writing HTTP responses to a temporary bytes.Buffer将 HTTP 响应写入临时 bytes.Buffer
【发布时间】:2025-11-30 21:50:02
【问题描述】:

我一直在进行一些分析和基准测试,以优化写入临时bytes.Buffer 以捕获来自template.ExecuteTemplate 的任何错误。

具体来说,我们正在写入缓冲区,检查是否有任何错误,如果没有,则写入我们的http.ResponseWriter。然而,问题在于临时缓冲区的请求开销有些明显:

  • 大约 6.2k req/s - 27.6k -> 21.4k(启用分析)和 29k -> 24k(不启用分析);
  • 每个请求延迟增加 9 毫秒(40 毫秒 -> 49 毫秒)。

当然,21k req/s 仍然是很多请求,但有 22% 的性能。 hit也是相当大的影响。

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
}

10K 缓冲区大小是对我的大多数响应的典型最大页面大小的粗略估计,尽管我还没有在少数几页之外对此进行测试。大于缓冲区大小的响应通常会导致性能再下降 20%。

有没有更好的方法在每个请求中写入临时缓冲区?另一个 gopher 指出了 Go 1.3 中即将推出的 sync.Pool,但我没有确定从哪里开始写出来。


添加:目前使用 http://godoc.org/github.com/oxtoacart/bpool 在每个请求 36ms 时产生 33k req/s:

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)

}

【问题讨论】:

  • 为什么不为缓冲区实现一个简单的池?只是一个简单的 FIFO 什么的?
  • @Not_a_Golfer 你能提供一个例子 sn-p 吗? play.golang.org 很好 - 只需要了解方法即可。
  • 这看起来会起作用(搜索 godoc 寻找其他一些替代方案):godoc.org/github.com/oxtoacart/bpool
  • 您可能还应该看到吞吐量的增加,无论大小如何,只需减少垃圾收集器的压力。
  • @Not_a_Golfer 这看起来非常积极(根据我的 Q 更新) - 比 写入临时缓冲区的示例要好。随意将您的评论复制并粘贴到答案中。

标签: go


【解决方案1】:

[复制自cmets作为答案]

只需使用非标准库中的可用池来池化您的缓冲区。这个看起来会起作用(搜索 godoc 寻找其他一些替代方案):

http://godoc.org/github.com/oxtoacart/bpool

您可能还应该看到吞吐量的增加,无论大小如何,只需减少垃圾收集器的压力即可。

【讨论】: