From 7d185acb41d9257997ae95e7459b24f3c5392f88 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Mon, 28 Apr 2025 11:58:05 +0200 Subject: [PATCH] Add button benchmark (#6854) This helped me benchmark the atomic layout (#5830) changes. I also realized that the label benchmark wasn't testing the painting, since the buttons at some point will be placed outside the screen_rect, meaning it won't be painted. This fixes it by benching the label in a child ui. The `label &str` benchmark went from 483 ns to 535 ns with these changes. EDIT: I fixed another benchmark problem, since the benchmark would show the same widget millions of times for a single frame, the WidgetRects hashmap would get huge, causing each iteration to slow down a bit more and causing the benchmark to have unreliable results. With this the `label &str` benchmark went from 535ns to 298ns. Also the `label format!` benchmark now takes almost the same time (302 ns). Before, it was a lot slower since it reused the same Context which already had millions of widget ids. --- crates/egui_demo_lib/README.md | 16 +++++ crates/egui_demo_lib/benches/benchmark.rs | 74 ++++++++++++++++++++--- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/crates/egui_demo_lib/README.md b/crates/egui_demo_lib/README.md index 43ca0a0f..58a5d630 100644 --- a/crates/egui_demo_lib/README.md +++ b/crates/egui_demo_lib/README.md @@ -14,3 +14,19 @@ The demo library is a separate crate for three reasons: * To remove the amount of code in `egui` proper. * To make it easy for 3rd party egui integrations to use it for tests. - See for instance https://github.com/not-fl3/egui-miniquad/blob/master/examples/demo.rs + +This crate also contains benchmarks for egui. +Run them with +```bash +# Run all benchmarks +cargo bench -p egui_demo_lib + +# Run a single benchmark +cargo bench -p egui_demo_lib "benchmark name" + +# Profile benchmarks with cargo-flamegraph (--root flag is necessary for MacOS) +CARGO_PROFILE_BENCH_DEBUG=true cargo flamegraph --bench benchmark --root -p egui_demo_lib -- --bench "benchmark name" + +# Profile with cargo-instruments +CARGO_PROFILE_BENCH_DEBUG=true cargo instruments --profile bench --bench benchmark -p egui_demo_lib -t time -- --bench "benchmark name" +``` diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index dab6bdd7..331788c9 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -1,11 +1,20 @@ use std::fmt::Write as _; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use egui::epaint::TextShape; +use egui::load::SizedTexture; +use egui::{Button, Id, TextureId, Ui, UiBuilder, Vec2}; use egui_demo_lib::LOREM_IPSUM_LONG; use rand::Rng as _; +/// Each iteration should be called in their own `Ui` with an intentional id clash, +/// to prevent the Context from building a massive map of `WidgetRects` (which would slow the test, +/// causing unreliable results). +fn create_benchmark_ui(ctx: &egui::Context) -> Ui { + Ui::new(ctx.clone(), Id::new("clashing_id"), UiBuilder::new()) +} + pub fn criterion_benchmark(c: &mut Criterion) { use egui::RawInput; @@ -55,17 +64,62 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let ctx = egui::Context::default(); let _ = ctx.run(RawInput::default(), |ctx| { - egui::CentralPanel::default().show(ctx, |ui| { - c.bench_function("label &str", |b| { - b.iter(|| { + c.bench_function("label &str", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { ui.label("the quick brown fox jumps over the lazy dog"); - }); - }); - c.bench_function("label format!", |b| { - b.iter(|| { + }, + BatchSize::LargeInput, + ); + }); + c.bench_function("label format!", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { ui.label("the quick brown fox jumps over the lazy dog".to_owned()); - }); - }); + }, + BatchSize::LargeInput, + ); + }); + }); + } + + { + let ctx = egui::Context::default(); + let _ = ctx.run(RawInput::default(), |ctx| { + let mut group = c.benchmark_group("button"); + + // To ensure we have a valid image, let's use the font texture. The size + // shouldn't be important for this benchmark. + let image = SizedTexture::new(TextureId::default(), Vec2::splat(16.0)); + + group.bench_function("1_button_text", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::new("Hello World")); + }, + BatchSize::LargeInput, + ); + }); + group.bench_function("2_button_text_image", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::image_and_text(image, "Hello World")); + }, + BatchSize::LargeInput, + ); + }); + group.bench_function("3_button_text_image_right_text", |b| { + b.iter_batched_ref( + || create_benchmark_ui(ctx), + |ui| { + ui.add(Button::image_and_text(image, "Hello World").right_text("⏵")); + }, + BatchSize::LargeInput, + ); }); }); }