Open oleg-jukovec opened 1 year ago
Right now output is different (with 15 allocs):
$ go test -bench BenchmarkClientSerialTyped -benchmem
BenchmarkClientSerialTyped-8 13958 77821 ns/op 920 B/op 15 allocs/op
Here are all allocations: 1) In https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/future.go#L120-L127
./future.go:122:8: &Future{} escapes to heap
./future.go:125:19: make([]*Response, 0) escapes to heap
We create pointers to the Future
and an array of pointers. They are stored in heap.
./request.go:864:12: new(SelectRequest) escapes to heap
./request.go:870:25: []interface {}{} escapes to heap
We allocate memory for the SelectRequest
.
We actually can just not initialize key
field and reduce total allocations by 1.
./connection.go:1263:17: make([]byte, length) escapes to heap
./connection.go:894:11: &Response{...} escapes to heap
Creating a pointer to a Response
. Later, this pointer might be used in the Future
-s AppendPush
and SetResponse
methods. They accept a pointer and change the value of the corresponding structure.
./response.go:288:19: func literal escapes to heap
Some other allocations happens inside of msgpack
package:
./decode.go:85:10: new(Decoder) escapes to heap
./decode.go:624:12: make([]byte, 0, 64) escapes to heap
./decode_string.go:59:16: string(b) escapes to heap
./decode_map.go:87:31: unexpectedCodeError{...} escapes to heap
Rest of the allocations (4) I could not find, perhaps they are somewhere deep inside of the msgpack
calls.
Or there might be some allocations tied to use of interface{}
as function arguments (Values stored in interfaces might be arbitrarily large, but only one word is dedicated to holding the value in the interface structure, so the assignment allocates a chunk of memory on the heap and records the pointer in the one-word slot.
from https://research.swtch.com/interfaces). And we use interfaces a lot in the functions. But this is not detected by the go build -gcflags='-m=3'
output.
You could also try to run test with memory profiler. See -memprofile
option to ensure that rest of allocation belongs to msgpack
:
Updated list of all 15 allocations:
1) 2 allocations for creating a channel here https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L879
2) 1 allocation while passing an argument to a goroutine function https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L882
3) 1 allocation when creating a pointer to a Response
struct https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L894
4) 2 allocations while calling NewFuture
https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L1284
https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/future.go#L120-L127
./future.go:122:8: &Future{} escapes to heap
./future.go:125:19: make([]*Response, 0) escapes to heap
5) 2 allocations while calling NewSelectRequest
https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/request.go#L862-L874
./request.go:864:12: new(SelectRequest) escapes to heap
./request.go:870:25: []interface {}{} escapes to heap
6) 1 allocation in In https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L1244-L1267
./connection.go:1263:17: make([]byte, length) escapes to heap
7) 2 allocations: 1 for creating a []byte
array and 1 for creating a pointer to Response
structure:
https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L1102
https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L1139
./connection.go:1102:21: make([]byte, 0, 128) escapes to heap
./connection.go:1139:12: &Response{...} escapes to heap:
Some other allocations happens inside of msgpack
package:
8) 1 allocation in https://github.com/vmihailenco/msgpack/blob/0ac2a568aee92ef815c8d3e06b3fb3e2f79c18f6/decode.go#L84-L88
./decode.go:85:10: new(Decoder) escapes to heap
9) 1 allocation in https://github.com/vmihailenco/msgpack/blob/0ac2a568aee92ef815c8d3e06b3fb3e2f79c18f6/decode.go#L617-L624
./decode.go:624:12: make([]byte, 0, 64) escapes to heap
10) 1 allocation in https://github.com/vmihailenco/msgpack/blob/0ac2a568aee92ef815c8d3e06b3fb3e2f79c18f6/decode_string.go#L54-L60
./decode_string.go:59:16: string(b) escapes to heap
11) 1 allocation in https://github.com/vmihailenco/msgpack/blob/0ac2a568aee92ef815c8d3e06b3fb3e2f79c18f6/decode_map.go#L72-L88
./decode_map.go:87:31: unexpectedCodeError{...} escapes to heap
15 allocations total.
Updated list of all 15 allocations:
1. 2 allocations for creating a channel here https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L879 2. 1 allocation while passing an argument to a goroutine function https://github.com/tarantool/go-tarantool/blob/d8df65dcd0f29a6a5d07472115cbcf2753b12609/connection.go#L882
It does not look like a per request allocations. But let stop here. Thank you!
I also want to note that msgpack supports zero resource allocation with sync.Pool, I think you can try using this implementation for decoding
code from msgpack
var decPool = sync.Pool{
New: func() interface{} {
return NewDecoder(nil)
},
}
func GetDecoder() *Decoder {
return decPool.Get().(*Decoder)
}
func PutDecoder(dec *Decoder) {
dec.r = nil
dec.s = nil
decPool.Put(dec)
}
Now we do a lot of allocations per a request:
There is no need for
0
, but with 80-20 rule we could have a soft goal8
allocs per a request.