Description
The problem
egui's TextEdit becomes sluggish when it holds large amounts of text; probably, this is due to it rendering all of the text without doing anything clever to cull text which doesn't make it on to the screen (apologies if this is the wrong terminology, I'm not an expert in graphics programming). Even without doing anything clever, there seem to be some optimisation opportunities inside the code, but I wanted to seek some guidance on whether this is worth doing or not before committing to working on this.
At least, with my test program (see end, adapted from #2799), when there are 15 million characters inside the widget, it is noticably laggy to use (>1s per character insert, compiled with --release
).
I've done some crude println!
profiling with std::time::Instant::now()
to find the hot loops, as I will detail below.
Related issues: #1196 #2799 #2906
Profiling
Here are the measured slow parts of the result of inserting one 'a' into the middle of the buffer (took around ~2s on release mode):
egui/crates/epaint/src/text/text_layout.rs
Lines 110 to 136 in 9478e50
Took ~0.5s to complete the loop, and reallocated paragraph.glyphs
23 times. I think with a call to Vec::reserve_exact
the allocator pressure can be eliminated entirely, as it should be possible to determine how big each vector is supposed to be.
Took ~0.5s to complete.
egui/crates/epaint/src/text/text_layout.rs
Lines 480 to 485 in 9478e50
Took ~1s to complete.
Test program
use eframe::egui::{self, CentralPanel, TextEdit};
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
..Default::default()
};
eframe::run_native(
"editor big file test",
options,
Box::new(|_cc| Box::<MyApp>::new(MyApp::new())),
)
}
struct MyApp {
text: String,
}
impl MyApp {
fn new() -> Self {
let bytes = (0..500000).flat_map(|_| (0u8..10));
let string: String = bytes.map(|b| format!(" {b:02x}")).collect();
MyApp { text: string }
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
let code_editor = TextEdit::multiline(&mut self.text)
.code_editor()
.desired_width(f32::INFINITY)
.desired_rows(40);
let _output = code_editor.show(ui);
});
}
}