Skip to content
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

Uses container/heap for DelayingQueue #45070

Merged
merged 2 commits into from
May 15, 2017

Conversation

alindeman
Copy link
Contributor

@alindeman alindeman commented Apr 28, 2017

The current implementation of DelayingQueue doesn't perform very well when a large number of items (at random delays) are inserted. The original authors seemed to be aware of this and noted it in a TODO comment. This is my attempt at switching the implementation to use a priority queue based on container/heap.

Benchmarks from before the change:

╰─ go test -bench=. -benchmem | tee /tmp/before.txt
BenchmarkDelayingQueue_AddAfter-8         300000            256824 ns/op             520 B/op          3 allocs/op
PASS
ok      k8s.io/kubernetes/staging/src/k8s.io/client-go/util/workqueue   77.237s

After:

╰─ go test -bench=. -benchmem | tee /tmp/after.txt
BenchmarkDelayingQueue_AddAfter-8         500000              3519 ns/op             406 B/op          4 allocs/op
PASS
ok      k8s.io/kubernetes/staging/src/k8s.io/client-go/util/workqueue   2.969s

Comparison:

╰─ benchcmp /tmp/before.txt /tmp/after.txt
benchmark                             old ns/op     new ns/op     delta
BenchmarkDelayingQueue_AddAfter-8     256824        3519          -98.63%

benchmark                             old allocs     new allocs     delta
BenchmarkDelayingQueue_AddAfter-8     3              4              +33.33%

benchmark                             old bytes     new bytes     delta
BenchmarkDelayingQueue_AddAfter-8     520           406           -21.92%

I also find the container/heap-based code a bit more easy to understand. The implementation of the PriorityQueue is based on the documentation for container/heap.

Feedback definitely welcomed. This is one of my first contributions.

NONE

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Apr 28, 2017
@k8s-ci-robot
Copy link
Contributor

Hi @alindeman. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with @k8s-bot ok to test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Apr 28, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. release-note-label-needed release-note-none Denotes a PR that doesn't merit a release note. and removed release-note-label-needed labels Apr 28, 2017
@grodrigues3
Copy link
Contributor

@k8s-bot ok to test

@k8s-ci-robot k8s-ci-robot removed the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label May 1, 2017
@alindeman
Copy link
Contributor Author

@k8s-bot gce etcd3 e2e test this

@alindeman
Copy link
Contributor Author

/assign @deads2k

return item
}
func (pq waitForPriorityQueue) Peek() interface{} {
return pq[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write up an example with the index field filled in? My cursory look at push, pop, and peek seem to suggest that we peek at pq[0], pop from pq[n-1], and push onto the end.

@deads2k
Copy link
Contributor

deads2k commented May 10, 2017

I like the change, but I'm confused by what appears to be an inconsistent push, pop, and peek.

@alindeman
Copy link
Contributor Author

Thanks for the review @deads2k.

The structure of the heap is a little confusing at first, I agree. But it is documented that the minimum element is always at index 0:

The minimum element in the tree is the root, at index 0.

Push and Pop do operate at the other 'end' of the heap, and container/heap will 'percolate' up or down the element into the correct location.

How do you best suggest I clarify this code? By example, do you mean an example test? I'd be happy to work one up if so.

readyEntries := 0
for _, entry := range q.waitingForAdd {
for waitingForQueue.Len() > 0 {
entry := waitingForQueue.Peek().(*waitFor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you peek at the [0] element here (which I think is the "earliest") and then you add the popped [n-1] element which I think is the latest.

Copy link
Contributor Author

@alindeman alindeman May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deads2k Gotcha, I agree this initially looks confusing but I think it's correct. I'll try to explain it as best I can here, but let me know if you think I can clarify or make the code more clear.

  • The container/heap docs state that "The minimum element in the tree is the root, at index 0."
  • But the container/heap#Interface docs state that the Pop method should "remove and return element Len() - 1." and that the Push method should "add x as element Len()"

Basically, the 0th element of the heap is the least element (in this case, the element with the timestamp that will occur next in time). Therefore that's the element we want to Peek. In the Pop case, though, the container/heap will have moved the minimum element to be temporarily at Len() - 1, so we pop off from that end. In the Push case, we add the new element to the 'end' of the heap and the element is percolated up to its correct position by container/heap.

I've created a simple integer-based heap on the playground that might demonstrate this more clearly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like this comment attached to the peek, push, pop method area.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@deads2k Sounds good. I've added a commit with some documentation. How do you think it reads?

@deads2k
Copy link
Contributor

deads2k commented May 11, 2017

this lgtm

@liggitt want a look?

pq[i].index = i
pq[j].index = j
}
func (pq *waitForPriorityQueue) Push(x interface{}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc that this is for the heap package to call, and heap.Push should be called instead?

item.index = n
*pq = append(*pq, item)
}
func (pq *waitForPriorityQueue) Pop() interface{} {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doc that this is for the heap package to call, and heap.Pop should be called instead (especially since it depends on the heap.Pop swap to make sense)

@alindeman
Copy link
Contributor Author

Thanks for the review @liggitt. I've now added some documentation to the functions as well. What do you think?


for n := 0; n < b.N; n++ {
data := fmt.Sprintf("%d", n)
q.AddAfter(data, time.Duration(r.Int63n(int64(10*time.Minute))))
Copy link
Member

@liggitt liggitt May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see removal (add N items, pop N items, etc) exercised as well... the new impl must sweep every pending item on every Pop() to update indices...

for q.Len() > 0 {
_, _ = q.Get()
}
}
Copy link
Member

@liggitt liggitt May 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, curious what the effect on the benchmark was (mind updating the description)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liggitt 👍 I'm glad you brought it up. I've updated the original description with the new benchmark. It appears to be pretty negligible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, thanks

@alindeman alindeman force-pushed the container-heap branch 2 times, most recently from f5e632a to 51dbb74 Compare May 13, 2017 20:42
@alindeman
Copy link
Contributor Author

@k8s-bot pull-kubernetes-federation-e2e-gce test this

@liggitt
Copy link
Member

liggitt commented May 14, 2017

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label May 14, 2017
@alindeman
Copy link
Contributor Author

@k8s-bot pull-kubernetes-federation-e2e-gce test this

1 similar comment
@deads2k
Copy link
Contributor

deads2k commented May 15, 2017

@k8s-bot pull-kubernetes-federation-e2e-gce test this

@alindeman
Copy link
Contributor Author

@deads2k Thanks, looks like we're fully green now. Let me know if you have any other feedback.

@deads2k
Copy link
Contributor

deads2k commented May 15, 2017

/approve

@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alindeman, deads2k, liggitt

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label May 15, 2017
@k8s-github-robot
Copy link

Automatic merge from submit-queue

@k8s-github-robot k8s-github-robot merged commit 9590b94 into kubernetes:master May 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants