Skip to content

editor: Improve find_matches and replace_all perf - #51941

Merged
smitbarmase merged 14 commits into
mainfrom
improve-replace-perf
May 6, 2026
Merged

editor: Improve find_matches and replace_all perf#51941
smitbarmase merged 14 commits into
mainfrom
improve-replace-perf

Conversation

@osiewicz

@osiewicz osiewicz commented Mar 19, 2026

Copy link
Copy Markdown
Member

Helps with #38927

  • editor: Add a benchmark for find/replace
  • text: batch fragment insertions before turning them into a SumTree

Context

How to Review

Self-Review Checklist

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Improved performance of "Replace All" in buffer search

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Mar 19, 2026
@zed-codeowner-coordinator
zed-codeowner-coordinator Bot requested a review from a team March 19, 2026 14:24
@zed-community-bot zed-community-bot Bot added the staff Pull requests authored by a current member of Zed staff label Mar 19, 2026
@github-actions github-actions Bot added size/M and removed size/M labels Mar 19, 2026
@smitbarmase
smitbarmase force-pushed the improve-replace-perf branch from 4d0f05f to 76778a4 Compare April 2, 2026 10:16
@osiewicz

osiewicz commented Apr 7, 2026

Copy link
Copy Markdown
Member Author

@smitbarmase we might want to re-evaluate our benchmarks now that #53340 has landed in main

@zelenenka zelenenka removed the size/M label Apr 9, 2026
@smitbarmase
smitbarmase force-pushed the improve-replace-perf branch from 81c4f40 to cfe4f54 Compare April 9, 2026 08:14
@smitbarmase

Copy link
Copy Markdown
Member
  1. main (a151def) without multibuffer: Speed up anchor resolution by avoiding an excerpts seek #53340:
Found 10325246 matches in 4.019269708s
Replaced 10325246 matches in 66.332331875s
  1. main (a151def):
Found 10325246 matches in 3.99869475s
Replaced 10325246 matches in 55.969271958s
  1. this PR rebased on main:
Found 10325246 matches in 351.910208ms
Replaced 10325246 matches in 37.082790125s

@zed-industries-bot

zed-industries-bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor
Messages
📖

This PR includes links to the following GitHub Issues: #38927
If this PR aims to close an issue, please include a Closes #ISSUE line at the top of the PR body.

Generated by 🚫 dangerJS against 82486c2

osiewicz and others added 11 commits May 6, 2026 12:26
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>
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
smitbarmase force-pushed the improve-replace-perf branch from 7c353cf to 4a2a0b6 Compare May 6, 2026 06:56
@smitbarmase smitbarmase changed the title editor: Improve replace_all perf editor: Improve find_matches and replace_all perf May 6, 2026
@smitbarmase

Copy link
Copy Markdown
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
smitbarmase added this pull request to the merge queue May 6, 2026
Merged via the queue into main with commit cc279a4 May 6, 2026
31 checks passed
@smitbarmase
smitbarmase deleted the improve-replace-perf branch May 6, 2026 09:31
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants