From 9427526a4191430f828129b7255353050d7899fc Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 24 Jun 2025 13:43:33 -0600 Subject: [PATCH] gpui: Clear the element arena after presenting the frame (#33338) This is an easy way to shave some microseconds off the critical path for frame rendering. On my machine this reduces typical frame rendering latency by ~100 microseconds, probably quite a bit more on slower machines. Here is how long it typically takes to drop elements from the arena, from a fairly brief run: ![image](https://github.com/user-attachments/assets/65cfd911-eccf-4393-887d-8cad2cd27148) Release Notes: - N/A --- crates/gpui/src/app.rs | 2 +- crates/gpui/src/window.rs | 34 ++++++++++++++++++++++--------- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 109d5e7454..1853e6e934 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -909,7 +909,7 @@ impl App { }) .collect::>() { - self.update_window(window, |_, window, cx| window.draw(cx)) + self.update_window(window, |_, window, cx| window.draw(cx).clear()) .unwrap(); } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f0f4579b29..0e3f5763da 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -210,6 +210,23 @@ thread_local! { pub(crate) static ELEMENT_ARENA: RefCell = RefCell::new(Arena::new(32 * 1024 * 1024)); } +/// Returned when the element arena has been used and so must be cleared before the next draw. +#[must_use] +pub struct ArenaClearNeeded; + +impl ArenaClearNeeded { + /// Clear the element arena. + pub fn clear(self) { + ELEMENT_ARENA.with_borrow_mut(|element_arena| { + let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.; + if percentage >= 80. { + log::warn!("elevated element arena occupation: {}.", percentage); + } + element_arena.clear(); + }) + } +} + pub(crate) type FocusMap = RwLock>; impl FocusId { @@ -968,8 +985,10 @@ impl Window { measure("frame duration", || { handle .update(&mut cx, |_, window, cx| { - window.draw(cx); + let arena_clear_needed = window.draw(cx); window.present(); + // drop the arena elements after present to reduce latency + arena_clear_needed.clear(); }) .log_err(); }) @@ -1730,7 +1749,7 @@ impl Window { /// Produces a new frame and assigns it to `rendered_frame`. To actually show /// the contents of the new [Scene], use [present]. #[profiling::function] - pub fn draw(&mut self, cx: &mut App) { + pub fn draw(&mut self, cx: &mut App) -> ArenaClearNeeded { self.invalidate_entities(); cx.entities.clear_accessed(); debug_assert!(self.rendered_entity_stack.is_empty()); @@ -1754,13 +1773,6 @@ impl Window { self.layout_engine.as_mut().unwrap().clear(); self.text_system().finish_frame(); self.next_frame.finish(&mut self.rendered_frame); - ELEMENT_ARENA.with_borrow_mut(|element_arena| { - let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.; - if percentage >= 80. { - log::warn!("elevated element arena occupation: {}.", percentage); - } - element_arena.clear(); - }); self.invalidator.set_phase(DrawPhase::Focus); let previous_focus_path = self.rendered_frame.focus_path(); @@ -1802,6 +1814,8 @@ impl Window { self.refreshing = false; self.invalidator.set_phase(DrawPhase::None); self.needs_present.set(true); + + ArenaClearNeeded } fn record_entities_accessed(&mut self, cx: &mut App) { @@ -3467,7 +3481,7 @@ impl Window { fn dispatch_key_event(&mut self, event: &dyn Any, cx: &mut App) { if self.invalidator.is_dirty() { - self.draw(cx); + self.draw(cx).clear(); } let node_id = self.focus_node_id_in_rendered_frame(self.focus); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f9a25b2018..1e3d648d42 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2199,7 +2199,7 @@ impl Workspace { // (Note that the tests always do this implicitly, so you must manually test with something like: // "bindings": { "g z": ["workspace::SendKeystrokes", ": j u"]} // ) - window.draw(cx); + window.draw(cx).clear(); } })?; }