-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Allow GC to collect unneeded slice elements #5804
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #5804 +/- ##
=======================================
- Coverage 84.5% 84.5% -0.1%
=======================================
Files 272 272
Lines 22804 22810 +6
=======================================
+ Hits 19287 19292 +5
- Misses 3171 3172 +1
Partials 346 346 |
sdk/trace/batch_span_processor.go
Outdated
@@ -280,6 +280,7 @@ func (bsp *batchSpanProcessor) exportSpans(ctx context.Context) error { | |||
// | |||
// It is up to the exporter to implement any type of retry logic if a batch is failing | |||
// to be exported, since it is specific to the protocol and backend being sent to. | |||
clear(bsp.batch) // Let GC collect objects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that not clearing via clear(bsp.batch)
and doing bsp.batch = bsp.batch[:0]
below is intentional to reuse memory and reduce number of heap allocation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two kinds of memory reuse.
If we did bsp.batch = nil
that would indeed throw away the slice's backing array. Instead, we reuse that backing array by re-slicing the slice to have zero length but keeping the capacity and array (bsp.batch[:0]
).
clear()
does not change the above.
What clear()
does is it erases the backing array elements (from zero to len()
) with zero values for the array type. IIRC (I'm not looking at the code right now) the type is an interface. So, instead of pointers to objects, we have nil
s there. And, hence, GC can collect those objects if they are not referenced anywhere anymore. Free memory can be reused.
If the slice was of a scalar type (e.g. int
), then clear()
would not do anything useful for GC. But for interfaces it does.
I hope this helps to understand why I think this change would be beneficial. Or did I misunderstand your concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Thanks for a great description.
Can you add benchstat
results to PR description to compare the performance difference caused by the change?
I think the same improvement can be done in other places where [:0]
is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a benchmark to the description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pellared I've looked at other [:0]
usage and made a few more improvements. PTAL.
// not pointers and they themselves do not contain pointer fields, | ||
// therefore the duplicate values do not need to be zeroed for them to be | ||
// garbage collected. | ||
clear(s.attributes[len(unique):]) // Erase unneeded elements to let GC collect objects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is technically correct but the conclusion is not. While there are no pointers, there are string
and interface{}
fields there:
- Strings contain pointers to their backing arrays that contain the actual bytes. Those arrays cannot be GCed if the string (with an embedded/hidden pointer) is reachable.
interface{}
can contain anything. Everything, except for pointers, in an interface is "boxed" - allocated on the heap and a pointer to that data is stored in theinterface{}
. If it's a pointer already, then it's not "boxed" but... it's a pointer and the destination cannot be GCed. So, in both cases we need to clear thoseinterface{}
fields.
This is documented at https://go.dev/wiki/SliceTricks:
... followed by examples of how zeroing out the slice elements solves the problem. This PR does the same.