Fix several small performance inefficiencies in hot paths - #61275
Merged
Conversation
summary_for_anchor::<usize> computes offset_for_anchor(anchor) and then calls text_summary_for_range(0..offset), which walks the rope again only to recompute the exact usize it was given — roughly four wasted O(log n) tree walks per conversion. Call offset_for_anchor directly in ToOffset for Anchor and FromAnchor for usize. This is the hottest anchor-resolution path; it runs on every selection resolution and countless render-path call sites. Other dimensions (PointUtf16, etc.) keep summary_for_anchor, since only the byte-offset dimension is redundant.
SharedString is backed by SmolStr, and SmolStr::from(String) copies into a fresh Arc<str> for strings beyond the inline limit, so line.clone().into() performed two allocations and copies per line. The line buffer is kept and clear()ed for reuse anyway; the clone existed only to feed the conversion. line.as_str().into() does one copy, with inline storage for short lines. This runs for every visible line on every frame, on the hottest text-layout path.
Lamport timestamps (value, replica_id) uniquely identify operations; duplicates are the same op and are deduped immediately after the sort. Stability therefore buys nothing, and sort_unstable_by_key avoids the allocation and extra work of a stable merge sort.
sort_*_by_key re-invokes the key function during comparisons, so each comparison cloned a PathKey (an Arc<RelPath> refcount bump). Sort with an explicit comparator instead.
Deferring a directory called new_jobs.remove(job_ix), shifting the tail of the vec on every deferred dir — quadratic within a single directory. new_jobs is already Vec<Option<ScanJob>> (a None slot is pushed for recursive symlinks) and is consumed with .flatten(), so setting the slot to None achieves the same result in O(1).
miguelraz
force-pushed
the
small-perf-wins
branch
from
July 19, 2026 01:14
138d25e to
37e81e8
Compare
anantdgoel
approved these changes
Jul 19, 2026
This was referenced Jul 31, 2026
jolutz
pushed a commit
to jolutz/zed
that referenced
this pull request
Aug 8, 2026
…ries#61275) # Objective Land five small, independent performance fixes found during an audit of hot paths (anchor resolution, line shaping, worktree scanning, and sorting in multibuffers). ## Solution Each fix is its own commit, so **this PR is best reviewed commit-by-commit** — every commit message contains the full reasoning for that change: - `text`: Avoid redundant rope traversal in `Anchor → usize` conversion — call `offset_for_anchor` directly instead of `summary_for_anchor`, which recomputed the same byte offset with ~4 extra O(log n) tree walks. This is the hottest anchor-resolution path. - `editor`: Avoid double allocation per shaped line — `line.as_str().into()` instead of `line.clone().into()`, which allocated twice per visible line, every frame. - `text`: Use `sort_unstable_by_key` in operation queue insertion — Lamport timestamps are unique keys and duplicates are deduped right after, so stability buys nothing. - `multi_buffer`: Sort with an explicit comparator instead of `sort_unstable_by_key`, which cloned a `PathKey` (`Arc` refcount bump) on every comparison. - `worktree`: Replace O(n²) `Vec::remove` in the deferred-directory pass with an O(1) `None` assignment — the vec is already `Vec<Option<ScanJob>>` and is consumed with `.flatten()`. ## Testing - No behavior changes intended; all changes are mechanical and tests affected crates pass: `text`, `editor`, `multi_buffer`, `worktree`. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Improved editor performance through several micro-optimizations in anchor resolution, line shaping, and worktree scanning.
playdohface
pushed a commit
to playdohface/zed
that referenced
this pull request
Aug 29, 2026
…ries#61275) # Objective Land five small, independent performance fixes found during an audit of hot paths (anchor resolution, line shaping, worktree scanning, and sorting in multibuffers). ## Solution Each fix is its own commit, so **this PR is best reviewed commit-by-commit** — every commit message contains the full reasoning for that change: - `text`: Avoid redundant rope traversal in `Anchor → usize` conversion — call `offset_for_anchor` directly instead of `summary_for_anchor`, which recomputed the same byte offset with ~4 extra O(log n) tree walks. This is the hottest anchor-resolution path. - `editor`: Avoid double allocation per shaped line — `line.as_str().into()` instead of `line.clone().into()`, which allocated twice per visible line, every frame. - `text`: Use `sort_unstable_by_key` in operation queue insertion — Lamport timestamps are unique keys and duplicates are deduped right after, so stability buys nothing. - `multi_buffer`: Sort with an explicit comparator instead of `sort_unstable_by_key`, which cloned a `PathKey` (`Arc` refcount bump) on every comparison. - `worktree`: Replace O(n²) `Vec::remove` in the deferred-directory pass with an O(1) `None` assignment — the vec is already `Vec<Option<ScanJob>>` and is consumed with `.flatten()`. ## Testing - No behavior changes intended; all changes are mechanical and tests affected crates pass: `text`, `editor`, `multi_buffer`, `worktree`. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Improved editor performance through several micro-optimizations in anchor resolution, line shaping, and worktree scanning.
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.
Objective
Land five small, independent performance fixes found during an audit of hot paths (anchor resolution, line shaping, worktree scanning, and sorting in multibuffers).
Solution
Each fix is its own commit, so this PR is best reviewed commit-by-commit — every commit message contains the full reasoning for that change:
text: Avoid redundant rope traversal inAnchor → usizeconversion — calloffset_for_anchordirectly instead ofsummary_for_anchor, which recomputed the same byte offset with ~4 extra O(log n) tree walks. This is the hottest anchor-resolution path.editor: Avoid double allocation per shaped line —line.as_str().into()instead ofline.clone().into(), which allocated twice per visible line, every frame.text: Usesort_unstable_by_keyin operation queue insertion — Lamport timestamps are unique keys and duplicates are deduped right after, so stability buys nothing.multi_buffer: Sort with an explicit comparator instead ofsort_unstable_by_key, which cloned aPathKey(Arcrefcount bump) on every comparison.worktree: Replace O(n²)Vec::removein the deferred-directory pass with an O(1)Noneassignment — the vec is alreadyVec<Option<ScanJob>>and is consumed with.flatten().Testing
text,editor,multi_buffer,worktree.Self-Review Checklist:
Release Notes: