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

map positions through changes in O(N) #7408

Closed
wants to merge 2 commits into from

Conversation

pascalkuthe
Copy link
Member

@pascalkuthe pascalkuthe commented Jun 20, 2023

Closes #7396

Previously we iterated the entire changset for every position we mapped through the changes. That made the mapping O(MN) when mapping multiple positions and O(N^2) for most mutlicursor operations but since we are basically always dealing with sorted lists we can do the two iterations in lockstep which allows O(M+N) mapping

With this PR the example from #7396 now is pretty usable and the mapping doesn't show up in the flamegraph anymore

@pascalkuthe pascalkuthe added E-medium Call for participation: Experience needed to fix: Medium / intermediate A-core Area: Helix core improvements S-waiting-on-review Status: Awaiting review from a maintainer. C-perf labels Jun 20, 2023
@pascalkuthe pascalkuthe added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from a maintainer. labels Jun 20, 2023
Copy link
Member

@dead10ck dead10ck left a comment

Choose a reason for hiding this comment

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

This is awesome! I haven't taken a close look yet, but at first glance, it unfortunately seems to squarely conflict with #7269, which relies on the fact that each range is mapped individually. 🤔 Hopefully there's an alternative I can do.

@pascalkuthe
Copy link
Member Author

pascalkuthe commented Jun 20, 2023

This is awesome! I haven't taken a close look yet, but at first glance, it unfortunately seems to squarely conflict with #7269, which relies on the fact that each range is mapped individually. 🤔 Hopefully there's an alternative I can do.

I hadn't reviewed that PR yet but I took a look and don't really think it conflicts. You are only mapping ranges trough a changeset with a single change (and do the offset tracking yourself so it's already O(N)). I deleted Range::map function because there were no uses left but if that PR still needs it then I can keep it for your used case and a documentation that it's O(N) and therefore O(MN) in loops.

This PR doesn't really remove anything. It basically just changes map_pos to operate on a sorted list instead of a single position which speedsup the most common usecase (mapping the selection trough a transaction without selsction). Mapping a single position is still possible (in fact mal_pos still exists since it's used in a few placea, its trivial to implement using once and behaves the exactly the same as on master)

@dead10ck
Copy link
Member

Hm, yeah, I think you're right, other than Range::map getting removed, maybe nothing really changes. Even now, Selection::map isn't used in the code path of insert_char or delete_char_backwards—both still compute the resulting selection by iterating linearly over the ranges. So if I'm understanding this right, this change mostly affects the application of a change set to the document's text, rather than the computation of the transactions to begin with.

@pascalkuthe
Copy link
Member Author

pascalkuthe commented Jun 21, 2023

Yeah this only affect commands which don't manually update the transaction (also diagnostic mapping and virtual text mapping). It does however affect the "default" codepath of insert_char on mastee since that relies on the automatic range mapping (using Transaction::insert which is fairly common my used).

Our two PRs are kind of similar in that you have also included a more efficent implementation of mapping selections trough transaction by accident (by storing an offset and calling map on a single element change set). Your PR also optimizes the insert_char usecase if I read it right since you changed the entire command to use the new transaction primitive.

This PR is still useful for diagnostic/virtual text mapping and quite a few commands that still use automatic mapping. The automatic mapping is also always used for all commands for views that aren't the current view (so the selection you supply only applies to the current view). In fact the code is currently a bit unoptimized and always performs the automatic for the current view too and later just overwrites with the manually supplied selection.

@dead10ck
Copy link
Member

This has made me realize that I think we're unnecessarily mapping the whole selection through the change set in the cases where the transaction explicitly specifies a new one.

for selection in self.selections.values_mut() {
*selection = selection
.clone()
// Map through changes
.map(transaction.changes())
// Ensure all selections across all views still adhere to invariants.
.ensure_invariants(self.text.slice(..));
}
// if specified, the current selection should instead be replaced by transaction.selection
if let Some(selection) = transaction.selection() {
self.selections.insert(
view_id,
selection.clone().ensure_invariants(self.text.slice(..)),
);
}

We essentially do that whole mapping and then immediately throw all that work away.

helix-core/src/selection.rs Outdated Show resolved Hide resolved
@archseer
Copy link
Member

This has made me realize that I think we're unnecessarily mapping the whole selection through the change set in the cases where the transaction explicitly specifies a new one.

The mapping still needs to happen for other views than the current one. We could speed things up slightly by removing the current view's selection from the selection set before doing the mapping if the transaction contains a selection

@pascalkuthe
Copy link
Member Author

I fixed a small oversight that was causing test failures and addressed the comments. Should be ready now

@pascalkuthe pascalkuthe added S-waiting-on-review Status: Awaiting review from a maintainer. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 22, 2023
dead10ck
dead10ck previously approved these changes Jun 23, 2023
Copy link
Member

@dead10ck dead10ck left a comment

Choose a reason for hiding this comment

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

🍓🫐🥞

Copy link
Contributor

@A-Walrus A-Walrus left a comment

Choose a reason for hiding this comment

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

Just some typos :)

helix-core/src/selection.rs Outdated Show resolved Hide resolved
helix-core/src/transaction.rs Outdated Show resolved Hide resolved
helix-core/src/transaction.rs Outdated Show resolved Hide resolved
Copy link
Member

@the-mikedavis the-mikedavis left a comment

Choose a reason for hiding this comment

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

some very minor comments but otherwise this looks great, nice work!

helix-core/src/transaction.rs Outdated Show resolved Hide resolved
helix-core/src/transaction.rs Outdated Show resolved Hide resolved
@@ -420,6 +420,7 @@ pub mod util {
return transaction;
}

Copy link
Member

Choose a reason for hiding this comment

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

Can you explain/add a comment why normalize isn't needed here? This is an additional invariant that we should document in places where no_normalize is used

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a comment but this was actually exiting behaviour. Really this isn't a selection we produce here at all, we need the mapped ranges for calculating the actual selecton below (which is normalized later). In the past I achieved that by calling `map_pos' on the ranges directly (below). Normalizing here not only has no point it would also break the association between tabstops and selections (if selections are merged/sorted). I just switched to the selection function here to also take advantage of the optimization here but really this is not a selection at all we are just updating a list of ranges.

@pascalkuthe pascalkuthe force-pushed the linear_mapping branch 2 times, most recently from d0b96e0 to 5755c92 Compare June 25, 2023 12:14
@the-mikedavis
Copy link
Member

Github is acting strange but this is now merged onto master as d491e23 and b33516f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Helix core improvements C-perf E-medium Call for participation: Experience needed to fix: Medium / intermediate S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hangs when select and edit in a 15MB file.
5 participants