-
Notifications
You must be signed in to change notification settings - Fork 491
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
fix: memory leaks. #2489
fix: memory leaks. #2489
Conversation
Could we extract the pattern? i.e.
We already have codegen tools for the various slice types and it would make me feel better. |
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.
LGTM, in short these buffers grow indefinitely so when we reslice them to only the end of the buffer we need to shift all the data to the front of the buffer otherwise Go cannot free the unused space.
These appends therefore do not do any new allocations rather move the data to the beginning of the slice
Also this repo should be review-protected! |
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.
see comment
Mostly I'm worried about someone relying on these slices not being mutated, if we had the pattern extracted that would prevent future errors. i.e. these two are not equivalent:
|
Possibly a better interface:
|
66a8c1e
to
42667a2
Compare
circularqueue.gen_test.go
Outdated
endL := len(v) + q.l - q.tail | ||
q.data = append(q.data[:0], append(v[endL:], append(q.data[q.head:q.tail], v[:endL]...)...)...) | ||
q.tail += endL | ||
break |
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.
This may be a bit overengineered, I ended up not needing more than 1 enqueue at a time. I am not sure if I should remove the ability to enqueue multiple items at once.
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 vote for single enqueue - feel free to save the multi-enqueue version somewhere in case we need it in the future. Much easier to test single enqueue.
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.
Easier for me to review as well 😄
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 := i // make i a local object | ||
item := circularIntPtr(&i) | ||
runtime.SetFinalizer(item, func(q circularIntPtr) { | ||
// if the finalizer is called, that means that the GC believes the objects should be freed |
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.
We use runtime.SetFinalizer
to test if the GC is seeing the objects for deletion.
// Can't trust lowMark until all parents have reported. | ||
// Remove any unneeded match points. | ||
n.matchGroupsBuffer[groupId] = matches[i:] | ||
} |
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.
This block above, looks like it creates duplicates when stuff hasn't reported. I removed it, it could be related to an issue that has been seen.
09a3ed3
to
b4c3bbb
Compare
|
||
// newSrcPointconstructs a Circular Queue | ||
// with a given buffer buf. It is ok for buf to be nil. | ||
func newSrcPointCircularQueue(buf ...srcPoint) *srcPointCircularQueue { |
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.
Looks like this is never called?
c6d819c
to
d0bd4a5
Compare
6465890
to
c43b954
Compare
|
||
// Peek peeks i ahead of the current head of queue. It should be used in conjunction with .Len() to prevent a panic. | ||
func (q *{{ $k }}CircularQueue) Peek(i int) {{ $k }} { | ||
p := q.head + i |
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.
Should we bounds check to make sure we don't get garbage?
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.
It's intended to be used in conjuction with .Len to avoid that.
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.
Ok added the bounds check.
c43b954
to
edff10c
Compare
circularqueue.gen.go.tmpl
Outdated
if q.head < q.tail{ | ||
copy(buf, q.data[q.head:q.tail]) | ||
} else { | ||
copy(buf[copy(buf, q.data[q.head:]):], q.data[:q.tail]) |
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.
This might be easier to understand written on more than one line:
partialLength := copy(buf, q.data[q.head:])
copy(buf[partialLength):], q.data[:q.tail])
circularqueue_test.go
Outdated
|
||
} | ||
|
||
//if q.Dequeue(1); q.Peek(-1) != q.Val() { |
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.
commented code ?
} | ||
} | ||
|
||
func Test_leakCircularBuf(t *testing.T) { |
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.
It might be easier to use Benchmarks for this sort of test, it gives you the number of allocations. (for the future, this is fine as-is)
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.
This isn't for testing allocations, but for testing to make sure things get properly de-allocated (a test against leaks).
type {{ $k }}CircularQueue struct { | ||
data []{{ $k }} | ||
head int | ||
tail int |
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.
invariant: tail = (head + l) % len(data)
right? Is it worth keeping it separately?
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.
Yes, its mostly so we don't have to do a mod over and over again, it isn't strictly necessary.
} | ||
|
||
// Peek peeks i ahead of the current head of queue. It should be used in conjunction with .Len() to prevent a panic. | ||
func (q *{{ $k }}CircularQueue) Peek(i int) {{ $k }} { |
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.
This might be better named At
since we're only showing one value at an index, Peek sounds like it should return a slice. Not a blocker.
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 I will keep it this same.
There were a number of memory leaks of the form of `a = a[i:]`. These were replaced with circular queue buffers, to prevent the leaks.
edff10c
to
85d0821
Compare
There were a number of memory leaks of the form of `a = a[i:]`. These were replaced with circular queue buffers, to prevent the leaks.
There were a number of memory leaks of the form of
a = a[i:]
.These were replaced with grow-able circular queue.