-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: gen/FromWire: Allocate pointer fields once #476
Draft
abhinav
wants to merge
1
commit into
dev
Choose a base branch
from
abg/decode-alloc-once
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
abhinav
force-pushed
the
abg/decode-alloc-once
branch
from
May 18, 2021 18:39
b566c43
to
4ec2c2a
Compare
Codecov Report
@@ Coverage Diff @@
## dev #476 +/- ##
==========================================
+ Coverage 79.02% 79.14% +0.12%
==========================================
Files 129 129
Lines 16092 16260 +168
==========================================
+ Hits 12716 12869 +153
- Misses 2066 2081 +15
Partials 1310 1310
Continue to review full report at Codecov.
|
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) ```
abhinav
force-pushed
the
abg/decode-alloc-once
branch
from
July 8, 2021 03:19
4ec2c2a
to
f1e6392
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
In FromWire, when we decode pointer fields, our code takes the form,
This results in an allocation for every optional field present over the
wire.
This changes how we generate FromWire for structs to instead allocate
space for all pointer fields once, and then re-use the same block.
Note that the pre-allocated struct also includes nested struct fields,
so we can avoid that allocation too.
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: