Skip to content

Conversation

notoriaga
Copy link
Contributor

@notoriaga notoriaga commented Oct 21, 2021

With this change cargo prod-run seems to hover around 5-6% cpu usage with a full tracking tab. The perf change wasn't as drastic on my machine initially, so if someone with windows could take a peek that would be great

Still need to figure out whats up with the benchmarks

while let Ok(state) = recv.wait() {
tab.shared_state.set_settings_state(Default::default());
tick(tab, state);
while let Ok(mut state) = recv.wait_mut() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This loop waits for the settings state to change, when it does it runs tick which does whatever action is queued up. I called tab.shared_state.set_settings_state(Default::default()); to clear the state so things are only done once. But that of course queues up another event so effectively it was busy waiting. Adding a variant of wait that returns the locked value rather than a clone lets you mutate in place avoiding sending another notificaition

fn clone(&self) -> WatchReceiver<T> {
WatchReceiver {
shared: Weak::clone(&self.shared),
shared: Arc::clone(&self.shared),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can't be a weak ref if we want to return borrowed data. I don't think it was actually needed anyway

Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the watched value doesn't somehow hold a reference to the watcher then we should be fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I think that should be impossible with the current setup. My original idea was that if you drop the watched value the receivers are useless so might as well free the memory. In practice I think the watched values and the receivers are going to be dropped at the same time anyway (when process_messages ends)

@notoriaga notoriaga requested review from a team and silverjam October 21, 2021 18:29
Copy link
Collaborator

@john-michaelburke john-michaelburke left a comment

Choose a reason for hiding this comment

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

LGTM

tick(tab, state);
while let Ok(mut state) = recv.wait_mut() {
let s = std::mem::take(&mut *state);
drop(state);
Copy link
Contributor

Choose a reason for hiding this comment

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

I still have trouble understanding these early drops, I suppose the take take allows us to reset state to a default value without trigger another update, but why is the early drop needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think otherwise we'd be holding the lock while tick is going, which can run for a while. By dropping the guard before tick the settings state can get updated by other threads while tick is running

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And yeah that's the idea of the take as well

Copy link
Contributor

@silverjam silverjam Oct 21, 2021

Choose a reason for hiding this comment

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

This is one area where rust gets a little magical with the deref trait. I guess the key here is we're dealing with two different objects, the guard and the object. We extract the object from the guard with the take (but that's made possible by the deref trait) and then we drop guard, to drop our hold on the lock.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes definitely not super intuitive. Added the comment and also split the state and guard into two variables which I think makes it more clear what's being taken and what's being dropped

fn clone(&self) -> WatchReceiver<T> {
WatchReceiver {
shared: Weak::clone(&self.shared),
shared: Arc::clone(&self.shared),
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as the watched value doesn't somehow hold a reference to the watcher then we should be fine.

Copy link
Contributor

@silverjam silverjam left a comment

Choose a reason for hiding this comment

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

I'd appreciate some comments around the intent of the take/drop calls, otherwise lgtm

@notoriaga notoriaga merged commit a73bd79 into main Oct 21, 2021
@notoriaga notoriaga deleted the steve/perf-fix branch October 21, 2021 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants