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
Dynamically determine the amount of work that can be performed in an endpoint iteration #1133
Conversation
c52eb0e
to
14237c5
Compare
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 idea here makes sense to me!
Please squash these commits together, since we usually introduce new code together with changes that use them; and please consider shortening the first line of the commit message a bit.
quinn/src/work_limiter.rs
Outdated
#[derive(Debug)] | ||
pub struct WorkLimiter { | ||
/// Whether to measure the required work time, or to use the previous estimates | ||
mode: LimiterMode, |
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.
Let's make these field names a bit more concise, similar to most of the code. LimiterMode
can just be called Mode
(it's private anyway) and we can cut work_item
from all field names. I think we can also do without the _nanos
suffix, keeping as documentation only.
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 made most of them shorter. I kept _nanos
since this is really something that I find immensly useful when looking at foreign code where the unit isn't clear, and have to point out in reviews constantly.
Also please keep in mind that there is a low downside of having longer names as long as they don't span the full line. Code readability wins in most cases by having them - and code is more often read than written..
14237c5
to
e32fa0d
Compare
CI failure is on the flaky h3 test. Can someone restart? |
Haven't carefully reviewed the implementation yet, but overall this looks reasonable to me. I'd be happier if we had empirical evidence of an environment where the hardcoded values are significantly bad, but that's somewhat mitigated by the good encapsulation here and the infrequent sampling. Gave CI a kick. |
Seems like even though I increased the timer and gave it a lot of grace time the test is still flaky on CI. Maybe the CI macos CI runner is rather overscheduled. I will look into in way to mock time for the test to de-flake it. |
Ultimately we want to get rid of these tests since they're a maintenance burden, we just need to investigate making up for any coverage losses that would entail. |
e32fa0d
to
07d5371
Compare
The last failure was about the newly added test, not about the H3 one. I made this one now deterministic (at the cost of some other ugliness in the implementation) |
quinn/src/work_limiter.rs
Outdated
limiter.finish_cycle(); | ||
|
||
assert!( | ||
approximates(initial_batches, EXPECTED_INITIAL_BATCHES), |
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.
Do we still need approximates
now that we're using mocked time?
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 kept it around to accomodate for potential rounding errors, but changed the tolerance for just 10%. Haven't tested whether it would work without it.
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.
works without, so I removed it in favor of assert_eq
07d5371
to
c69e73c
Compare
…::drive_recv` dynamically This change adds a `WorkLimiter` component, which measures the amount of time required to perform some work items and will limit work based on time instead of pure iterations. It also changes the `Endpoint`s `drive_recv` method to limit receive operations based on the amount of spent time (to 50µs) using the `WorkLimiter`, instead of using the hardcoded `IO_LOOP_BOUND` counter. Performance differences are negligible on this machine (probably because `IO_LOOP_BOUND` was set to a number which works for it), but it can improve things on less known environments. I instrumented the endpoints receive method to see how much time it spends on average in `drive_recv`. **Baseline:** ``` Recv time: AvgTime { total: 3.280880841s, calls: 34559, avg: 94.935µs, min: 3.146µs, max: 312.574µs } path: PathStats { rtt: 511.656µs, ``` **With this change:** ``` Recv time: AvgTime { total: 3.333642823s, calls: 54627, avg: 61.024µs, min: 2.645µs, max: 319.147µs } path: PathStats { rtt: 446.641µs, ``` Note that 50µs are not reached because a single `recvmmsg` batch takes about 30µs, so this is just rounding up to 2 batches. **When set to 200µs (for comparison purposes):** ``` Recv time: AvgTime { total: 3.243954076s, calls: 19558, avg: 165.862µs, min: 2.525µs, max: 358.711µs } path: PathStats { rtt: 700.34µs, } ```
c69e73c
to
35904af
Compare
I'm not sure what happened to those macos tests:
However I feel like those failures are unrelated to these changes |
Must be a MacOS kernel change... |
CI failure was most likely caused by an issue in mio 0.7.12 which was picked up by the build: tokio-rs/mio#1497 That version is now yanked, so retrying the run might lead to success |
Yep, good find. |
While the hardcoded numbers might work good on some machines, they can be problematic on others.
They also make it harder to distinguish on how much time is spent on send vs receive.
This introduces a dynamic
WorkLimiter
which measures the elapsed time per operation, and allows to configure how much time we intend to spend on send vs receive.This change integrates it so far for
recv
which is at the moment more problematic, but it can easily be extended for the send path.Performance differences are negligible on this machine (probably because
IO_LOOP_BOUND
was set to a number which works for it), but it can improvethings on less known environments.
I instrumented the endpoints receive method to see how much time it spends
on average in
drive_recv
.Baseline:
With this change:
Note that 50µs are not reached because a single
recvmmsg
batch takes about 30µs, so this is just rounding up to 2 batches.When set to 200µs (for comparison purposes):