editor: Improve find_matches and replace_all perf - #51941
Merged
Conversation
smitbarmase
force-pushed
the
improve-replace-perf
branch
from
April 2, 2026 10:16
4d0f05f to
76778a4
Compare
Member
Author
|
@smitbarmase we might want to re-evaluate our benchmarks now that #53340 has landed in main |
smitbarmase
force-pushed
the
improve-replace-perf
branch
from
April 9, 2026 08:14
81c4f40 to
cfe4f54
Compare
Member
|
Contributor
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit introduces a FragmentBuilder which reduces the time needed to run a replace_all on large files by 30%. It does pretty much what a SumTree would, except that it doesn't have to rebalance the tree on each insertion Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
cc @cole-miller Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
smitbarmase
force-pushed
the
improve-replace-perf
branch
from
May 6, 2026 06:56
7c353cf to
4a2a0b6
Compare
Member
smit@lemon ~/w/zed (improve-replace-perf)> ./target/release/editor_benchmarks /Users/smit/Downloads/big-file.csv '"' --replace '""'
Read /Users/smit/Downloads/big-file.csv (619093537 bytes)
Finding matches...
Found 10325246 matches in 379.045917ms
Replacing all matches...
Replaced 10325246 matches in 38.657013583s |
smitbarmase
approved these changes
May 6, 2026
ebaah46
pushed a commit
to ebaah46/zed
that referenced
this pull request
May 6, 2026
Helps with zed-industries#38927 - **editor: Add a benchmark for find/replace** - **text: batch fragment insertions before turning them into a SumTree** ## Context <!-- What does this PR do, and why? How is it expected to impact users? Not just what changed, but what motivated it and why this approach. Link to Linear issue (e.g., ENG-123) or GitHub issue (e.g., Closes zed-industries#456) if one exists — helps with traceability. --> ## How to Review <!-- Help reviewers focus their attention: - For small PRs: note what to focus on (e.g., "error handling in foo.rs") - For large PRs (>400 LOC): provide a guided tour — numbered list of files/commits to read in order. (The `large-pr` label is applied automatically.) - See the review process guidelines for comment conventions --> ## Self-Review Checklist <!-- Check before requesting review: --> - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved performance of "Replace All" in buffer search --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This was referenced May 13, 2026
pull Bot
pushed a commit
to edisplay/zed
that referenced
this pull request
Jun 5, 2026
…ed-industries#58681) ## Context `apply_remote_edit` and `apply_local_edit` rebuild the fragment `SumTree` through `FragmentBuilder`. Since zed-industries#51941, the builder collapsed the entire tree into a flat `Vec<Fragment>` (cloning every fragment) and rebuilt a fresh tree from scratch on every call. The new tree shared no nodes with the previous one, so each edit cost O(N) in the *total* number of fragments: clone all fragments, allocate an entirely new tree, and then free the whole previous tree on assignment — the last part showing up as a large amount of time spent dropping `SumTree`s. That batching was a deliberate win for the bulk `replace_all` path (zed-industries#51941, one `edit()` carrying millions of ranges), but it penalizes every other caller — most notably `apply_remote_edit`, which runs once per op in a loop in `apply_ops` and so rebuilt the whole tree per remote operation. ## This change Make `FragmentBuilder` chunk-based. Appended slices are kept as intact `SumTree` subtrees, so they keep sharing nodes with the previous tree; only individually pushed fragments are batched into `Vec`s. `to_sum_tree` appends the shared subtrees (touching just the right spine) and builds the loose runs in one pass, parallelizing the large ones. Net effect: - Small edits on large/heavily-fragmented buffers (the `apply_remote_edit` collaborative path) go back to O(edited + log N) with a cheap drop, instead of O(total fragments). - The bulk `replace_all` path keeps its batched build and is not regressed. ## Benchmarks Using the file from zed-industries#38927 (619 MB CSV, 10,325,246 matches of `"` → `""`), `release-fast`: | Case | flatten (zed-industries#51941) | this PR | |---|---|---| | `replace_all` (all matches) | 50.70 s | 45.16 s | | replace 1 match (`--single`) | 16.0 ms | 16.0 ms | `replace_all` is ~11% faster and not regressed. The single-match-on-a-freshly-loaded-file case is unchanged, because a fresh `Buffer::local` is barely fragmented, so even the from-scratch rebuild is cheap there; the structural-sharing win is on heavily-fragmented buffers receiving small edits. The `--single` flag added to `editor_benchmarks` makes the latter case measurable. Release Notes: - Improved the performance of applying edits to large buffers --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TomPlanche
pushed a commit
to TomPlanche/zed
that referenced
this pull request
Jun 8, 2026
…ed-industries#58681) ## Context `apply_remote_edit` and `apply_local_edit` rebuild the fragment `SumTree` through `FragmentBuilder`. Since zed-industries#51941, the builder collapsed the entire tree into a flat `Vec<Fragment>` (cloning every fragment) and rebuilt a fresh tree from scratch on every call. The new tree shared no nodes with the previous one, so each edit cost O(N) in the *total* number of fragments: clone all fragments, allocate an entirely new tree, and then free the whole previous tree on assignment — the last part showing up as a large amount of time spent dropping `SumTree`s. That batching was a deliberate win for the bulk `replace_all` path (zed-industries#51941, one `edit()` carrying millions of ranges), but it penalizes every other caller — most notably `apply_remote_edit`, which runs once per op in a loop in `apply_ops` and so rebuilt the whole tree per remote operation. ## This change Make `FragmentBuilder` chunk-based. Appended slices are kept as intact `SumTree` subtrees, so they keep sharing nodes with the previous tree; only individually pushed fragments are batched into `Vec`s. `to_sum_tree` appends the shared subtrees (touching just the right spine) and builds the loose runs in one pass, parallelizing the large ones. Net effect: - Small edits on large/heavily-fragmented buffers (the `apply_remote_edit` collaborative path) go back to O(edited + log N) with a cheap drop, instead of O(total fragments). - The bulk `replace_all` path keeps its batched build and is not regressed. ## Benchmarks Using the file from zed-industries#38927 (619 MB CSV, 10,325,246 matches of `"` → `""`), `release-fast`: | Case | flatten (zed-industries#51941) | this PR | |---|---|---| | `replace_all` (all matches) | 50.70 s | 45.16 s | | replace 1 match (`--single`) | 16.0 ms | 16.0 ms | `replace_all` is ~11% faster and not regressed. The single-match-on-a-freshly-loaded-file case is unchanged, because a fresh `Buffer::local` is barely fragmented, so even the from-scratch rebuild is cheap there; the structural-sharing win is on heavily-fragmented buffers receiving small edits. The `--single` flag added to `editor_benchmarks` makes the latter case measurable. Release Notes: - Improved the performance of applying edits to large buffers --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jonx
pushed a commit
to jonx/zed-aros
that referenced
this pull request
Jul 17, 2026
Helps with zed-industries#38927 - **editor: Add a benchmark for find/replace** - **text: batch fragment insertions before turning them into a SumTree** ## Context <!-- What does this PR do, and why? How is it expected to impact users? Not just what changed, but what motivated it and why this approach. Link to Linear issue (e.g., ENG-123) or GitHub issue (e.g., Closes zed-industries#456) if one exists — helps with traceability. --> ## How to Review <!-- Help reviewers focus their attention: - For small PRs: note what to focus on (e.g., "error handling in foo.rs") - For large PRs (>400 LOC): provide a guided tour — numbered list of files/commits to read in order. (The `large-pr` label is applied automatically.) - See the review process guidelines for comment conventions --> ## Self-Review Checklist <!-- Check before requesting review: --> - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved performance of "Replace All" in buffer search --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
jonx
pushed a commit
to jonx/zed-aros
that referenced
this pull request
Jul 17, 2026
…ed-industries#58681) ## Context `apply_remote_edit` and `apply_local_edit` rebuild the fragment `SumTree` through `FragmentBuilder`. Since zed-industries#51941, the builder collapsed the entire tree into a flat `Vec<Fragment>` (cloning every fragment) and rebuilt a fresh tree from scratch on every call. The new tree shared no nodes with the previous one, so each edit cost O(N) in the *total* number of fragments: clone all fragments, allocate an entirely new tree, and then free the whole previous tree on assignment — the last part showing up as a large amount of time spent dropping `SumTree`s. That batching was a deliberate win for the bulk `replace_all` path (zed-industries#51941, one `edit()` carrying millions of ranges), but it penalizes every other caller — most notably `apply_remote_edit`, which runs once per op in a loop in `apply_ops` and so rebuilt the whole tree per remote operation. ## This change Make `FragmentBuilder` chunk-based. Appended slices are kept as intact `SumTree` subtrees, so they keep sharing nodes with the previous tree; only individually pushed fragments are batched into `Vec`s. `to_sum_tree` appends the shared subtrees (touching just the right spine) and builds the loose runs in one pass, parallelizing the large ones. Net effect: - Small edits on large/heavily-fragmented buffers (the `apply_remote_edit` collaborative path) go back to O(edited + log N) with a cheap drop, instead of O(total fragments). - The bulk `replace_all` path keeps its batched build and is not regressed. ## Benchmarks Using the file from zed-industries#38927 (619 MB CSV, 10,325,246 matches of `"` → `""`), `release-fast`: | Case | flatten (zed-industries#51941) | this PR | |---|---|---| | `replace_all` (all matches) | 50.70 s | 45.16 s | | replace 1 match (`--single`) | 16.0 ms | 16.0 ms | `replace_all` is ~11% faster and not regressed. The single-match-on-a-freshly-loaded-file case is unchanged, because a fresh `Buffer::local` is barely fragmented, so even the from-scratch rebuild is cheap there; the structural-sharing win is on heavily-fragmented buffers receiving small edits. The `--single` flag added to `editor_benchmarks` makes the latter case measurable. Release Notes: - Improved the performance of applying edits to large buffers --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
Helps with zed-industries#38927 - **editor: Add a benchmark for find/replace** - **text: batch fragment insertions before turning them into a SumTree** ## Context <!-- What does this PR do, and why? How is it expected to impact users? Not just what changed, but what motivated it and why this approach. Link to Linear issue (e.g., ENG-123) or GitHub issue (e.g., Closes zed-industries#456) if one exists — helps with traceability. --> ## How to Review <!-- Help reviewers focus their attention: - For small PRs: note what to focus on (e.g., "error handling in foo.rs") - For large PRs (>400 LOC): provide a guided tour — numbered list of files/commits to read in order. (The `large-pr` label is applied automatically.) - See the review process guidelines for comment conventions --> ## Self-Review Checklist <!-- Check before requesting review: --> - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Improved performance of "Replace All" in buffer search --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
…ed-industries#58681) ## Context `apply_remote_edit` and `apply_local_edit` rebuild the fragment `SumTree` through `FragmentBuilder`. Since zed-industries#51941, the builder collapsed the entire tree into a flat `Vec<Fragment>` (cloning every fragment) and rebuilt a fresh tree from scratch on every call. The new tree shared no nodes with the previous one, so each edit cost O(N) in the *total* number of fragments: clone all fragments, allocate an entirely new tree, and then free the whole previous tree on assignment — the last part showing up as a large amount of time spent dropping `SumTree`s. That batching was a deliberate win for the bulk `replace_all` path (zed-industries#51941, one `edit()` carrying millions of ranges), but it penalizes every other caller — most notably `apply_remote_edit`, which runs once per op in a loop in `apply_ops` and so rebuilt the whole tree per remote operation. ## This change Make `FragmentBuilder` chunk-based. Appended slices are kept as intact `SumTree` subtrees, so they keep sharing nodes with the previous tree; only individually pushed fragments are batched into `Vec`s. `to_sum_tree` appends the shared subtrees (touching just the right spine) and builds the loose runs in one pass, parallelizing the large ones. Net effect: - Small edits on large/heavily-fragmented buffers (the `apply_remote_edit` collaborative path) go back to O(edited + log N) with a cheap drop, instead of O(total fragments). - The bulk `replace_all` path keeps its batched build and is not regressed. ## Benchmarks Using the file from zed-industries#38927 (619 MB CSV, 10,325,246 matches of `"` → `""`), `release-fast`: | Case | flatten (zed-industries#51941) | this PR | |---|---|---| | `replace_all` (all matches) | 50.70 s | 45.16 s | | replace 1 match (`--single`) | 16.0 ms | 16.0 ms | `replace_all` is ~11% faster and not regressed. The single-match-on-a-freshly-loaded-file case is unchanged, because a fresh `Buffer::local` is barely fragmented, so even the from-scratch rebuild is cheap there; the structural-sharing win is on heavily-fragmented buffers receiving small edits. The `--single` flag added to `editor_benchmarks` makes the latter case measurable. Release Notes: - Improved the performance of applying edits to large buffers --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Helps with #38927
Context
How to Review
Self-Review Checklist
Release Notes: