My main concern is using ioutil.ReadAll
everytime the bufio.Reader
is read in parseGetResponse
since it will then trigger bytes.makeSlice
and allocate new space each and every time the function is called.
Instead I think using a buffer pool like fmt
does would be a fairly better solution.
I've added a benchmark in memcache_test.go
that Set
4 []byte of different sizes in memcache, and then randomly Get
them. Here's the result before using the buffer pool:
$ go test ./memcache -v -run=^$ -bench=BenchmarkGet$ -benchtime=10s -parallel=4 -memprofile=prof.mem
PASS
BenchmarkGet-4 200000 53816 ns/op 74349 B/op 36 allocs/op
$ go tool pprof --alloc_space memcache.test prof.mem
(pprof) list parseGetResponse
Total: 15.85GB
ROUTINE ======================== github.com/asticode/gomemcache/memcache.parseGetResponse in /home/asticode/projects/go/src/github.com/asticode/gomemcache/memcache/memcache.go
51.50MB 15.41GB (flat, cum) 97.23% of Total
. . 453: return err
. . 454: }
. . 455: if bytes.Equal(line, resultEnd) {
. . 456: return nil
. . 457: }
46MB 46MB 458: it := new(Item)
. 66MB 459: size, err := scanGetResponseLine(line, it)
. . 460: if err != nil {
. . 461: return err
. . 462: }
5.50MB 15.30GB 463: it.Value, err = ioutil.ReadAll(io.LimitReader(r, int64(size)+2))
. . 464: if err != nil {
. . 465: return err
. . 466: }
. . 467: if !bytes.HasSuffix(it.Value, crlf) {
. . 468: return fmt.Errorf("memcache: corrupt get result read")
And here's the result after using the buffer pool:
$ go test ./memcache -v -run=^$ -bench=BenchmarkGet$ -benchtime=10s -parallel=4 -memprofile=prof.mem
PASS
BenchmarkGet-4 500000 27828 ns/op 1082 B/op 31 allocs/op
$ go tool pprof --alloc_space memcache.test prof.mem
(pprof) list parseGetResponse
Total: 549.90MB
ROUTINE ======================== github.com/asticode/gomemcache/memcache.parseGetResponse in /home/asticode/projects/go/src/github.com/asticode/gomemcache/memcache/memcache.go
49MB 341.24MB (flat, cum) 62.06% of Total
. . 466: return err
. . 467: }
. . 468: if bytes.Equal(line, resultEnd) {
. . 469: return nil
. . 470: }
33.50MB 33.50MB 471: it := new(Item)
. 115.50MB 472: size, err := scanGetResponseLine(line, it)
. . 473: if err != nil {
. . 474: return err
. . 475: }
. . 476: buf := bufferPool.Get().(*bytes.Buffer)
15.50MB 192.23MB 477: _, err = buf.ReadFrom(io.LimitReader(r, int64(size)+2))
. . 478: if err != nil {
. . 479: freeBuffer(buf)
. . 480: return err
. . 481: }
. . 482: it.Value = buf.Bytes()
cla: yes