将HTTP响应写入一个临时字节。

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)

}

[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.