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

Speed up watch expression eval #5226

Merged
merged 1 commit into from
Jul 15, 2024
Merged

Speed up watch expression eval #5226

merged 1 commit into from
Jul 15, 2024

Conversation

ChrisPenner
Copy link
Contributor

@ChrisPenner ChrisPenner commented Jul 15, 2024

Overview

Noticed that the eval of very simple watch expressions was a bit slower than expected and dug into it, found a quick fix and reduced the watch expression time for this particular case by 90%; should have similar speedups for any expressions with deep transitive dependencies.

I ran a test locally with a single top-level example expression that had a deep dependency tree, and a single watch expression.

Here's how the timings compare on a couple runs (after runtime warmup)

trunk:
Finished Collect deps in 0.951952s (cpu), 0.951724s (system)
Finished Collect deps in 0.956545s (cpu), 0.958644s (system)

this branch:
Finished Collect deps in 0.122703s (cpu), 0.122523s (system)
Finished Collect deps in 0.14749s (cpu), 0.163158s (system)

Implementation notes

There was a subtlety in the optimization of recursive dependency search in the runtime which meant that tree pruning wasn't shared amongst multiple branches. I swapped it to a state monad rather than explicit binding passing to ensure the 'seen' Set is propagated everywhere it needs to go.

Test coverage

Local testing using UNISON_DEBUG=TIMING

Loose ends

Could probably do even better by only collecting dependencies of the watch expressions themselves, I don't think we need the dependencies of parts of the file that we don't actually touch. Would that work @dolio ?

I'd also be curious if we could delay the code lookup until execution time so we only load terms that are literally required by the exact evaluation of the watch expression, but I'm guessing this would be a pain to write because we'd need separate codepaths for the watch expr eval vs the version that runs from a file or .uc or w/e

rec <- for (toList newDeps) $ \case
RF.DerivedId i ->
getTypeDeclaration cl i >>= \case
Just d -> recursiveDeclDeps seen cl d
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we should have been collecting the 'seen' results from this recursive call to pass on to the next iteration of the for loop. The state monad now handles this for us.

rec <- for (toList (deps \\ seen0)) $ \case
RF.ConReference (RF.ConstructorReference (RF.DerivedId refId) _conId) _conType -> handleTypeReferenceId refId
RF.TypeReference (RF.DerivedId refId) -> handleTypeReferenceId refId
RF.TermReference r -> recursiveRefDeps seen cl r
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we should have been collecting the 'seen' results from this recursive call to pass on to the next iteration of the for loop. The state monad now handles this for us.

@ChrisPenner ChrisPenner marked this pull request as ready for review July 15, 2024 18:29
@ChrisPenner ChrisPenner requested a review from aryairani July 15, 2024 18:43
@aryairani aryairani merged commit b65836b into trunk Jul 15, 2024
35 checks passed
@aryairani aryairani deleted the cp/faster-dep-collection branch July 15, 2024 20:27
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.

2 participants