elithrar
elithrar

Reputation: 24260

Writing HTTP responses to a temporary bytes.Buffer

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:

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)

}

Upvotes: 1

Views: 3272

Answers (1)

Not_a_Golfer
Not_a_Golfer

Reputation: 49205

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

Upvotes: 3

Related Questions