Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gen/FromWire: Allocate pointer fields once
In FromWire, when we decode pointer fields, our code takes the form, ``` name := value.GetString() // Given (value wire.Value) user.Name = &name ``` This results in an allocation for every optional field present over the wire. name := value.GetString() user.Name = &name // alloc // ... email := value.GetString() user.Email = &email // alloc // ... age := value.GetInt8() user.Age = &age // alloc This changes how we generate FromWire for structs to instead allocate space for all pointer fields once, and then re-use the same block. var ptrFields struct { Name string Email string Age int8 } // alloc ptrFields.Name = value.GetString() user.Name = &ptrFields.Name // no alloc ptrFields.Email = value.GetString() user.Email = &ptrFields.Email // no alloc ptrFields.Age = value.GetInt8() user.Age = &ptrFields.Age // no alloc Note that the pre-allocated struct also includes nested struct fields, so we can avoid that allocation too. user.Comment = _Comment_Read(value) // func _Comment_Read(value wire.Value) *Comment { // var c Comment // c.FromWire(value) // return &c // alloc // } // Becomes, var ptrFields struct { // ... Comment Comment } ptrFields.Comment.FromWire(value) user.Comment = &ptrFields.Comment // no alloc This will have the following effects: Structs with optional primitive fields, or other structs inside them will go from N small allocations to one big allocation. For most structs, the total amount of space allocated will remain largely the same. However, for sparse structs with lots of optional fields where only a handful of them are set, this will allocate more bytes and therefore, hold onto more memory than they need. We think that since the majority case is going to be structs with most of their fields filled in, reducing the number of allocations takes precedence. Performance: ``` $ git co master $ go test -run '^$' -bench ././Decode -benchmem -count 5 > before.txt $ git co - $ go test -run '^$' -bench ././Decode -benchmem -count 5 > after.txt $ benchstat before.txt after.txt name old time/op new time/op delta RoundTrip/PrimitiveOptionalStruct/Decode-4 2.93µs ± 5% 2.81µs ± 6% ~ (p=0.095 n=5+5) RoundTrip/Graph/Decode-4 7.30µs ±17% 6.16µs ± 8% -15.60% (p=0.032 n=5+5) RoundTrip/ContainersOfContainers/Decode-4 52.7µs ± 1% 57.7µs ±12% ~ (p=0.730 n=4+5) name old alloc/op new alloc/op delta RoundTrip/PrimitiveOptionalStruct/Decode-4 1.40kB ± 0% 1.41kB ± 0% +0.43% (p=0.008 n=5+5) RoundTrip/Graph/Decode-4 2.78kB ± 0% 2.78kB ± 0% ~ (all equal) RoundTrip/ContainersOfContainers/Decode-4 12.3kB ± 0% 12.3kB ± 0% ~ (p=0.881 n=5+5) name old allocs/op new allocs/op delta RoundTrip/PrimitiveOptionalStruct/Decode-4 14.0 ± 0% 8.0 ± 0% -42.86% (p=0.008 n=5+5) RoundTrip/Graph/Decode-4 32.0 ± 0% 29.0 ± 0% -9.38% (p=0.008 n=5+5) RoundTrip/ContainersOfContainers/Decode-4 164 ± 0% 164 ± 0% ~ (p=0.556 n=5+4) ```
- Loading branch information