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
This commit is contained in:
Michael Sloan 2025-06-24 13:43:33 -06:00 committed by GitHub
parent eec26c9a41
commit 9427526a41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 26 additions and 12 deletions

View file

@ -909,7 +909,7 @@ impl App {
})
.collect::<Vec<_>>()
{
self.update_window(window, |_, window, cx| window.draw(cx))
self.update_window(window, |_, window, cx| window.draw(cx).clear())
.unwrap();
}

View file

@ -210,6 +210,23 @@ thread_local! {
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = 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<SlotMap<FocusId, AtomicUsize>>;
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);

View file

@ -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 <enter> u"]}
// )
window.draw(cx);
window.draw(cx).clear();
}
})?;
}