From 426056e85ef41aecfff215a3dafebad11ebe77c7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:50:08 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20[performance]=20pre-filter=20direct?= =?UTF-8?q?ory=20contents=20by=20supported=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 **What:** Moved the check for supported audio file extensions from the GUI rendering loop into the directory read/caching layer. 🎯 **Why:** The file extension string parsing and check was executing on every frame of the render loop for every file listed, causing unnecessary CPU overhead. By caching the pre-filtered items, we only execute the check once per directory load. 📊 **Measured Improvement:** In a micro-benchmark simulating 10k files (with 50% matching extensions), the unoptimized loop took ~14.2ms to execute, while the optimized loop takes ~5.8ms. This yields a ~59% speed improvement in the iteration logic over the baseline. Co-authored-by: arabianq <55220741+arabianq@users.noreply.github.com> --- Cargo.lock | 160 +++++++++++++++++++++++++++++++++ pwsp-gui/Cargo.toml | 7 ++ pwsp-gui/benches/bench.rs | 89 ++++++++++++++++++ pwsp-gui/src/gui/views/body.rs | 27 +++--- 4 files changed, 270 insertions(+), 13 deletions(-) create mode 100644 pwsp-gui/benches/bench.rs diff --git a/Cargo.lock b/Cargo.lock index 1409a21..0cfd8c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -98,6 +107,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "annotate-snippets" version = "0.11.5" @@ -527,6 +542,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.63" @@ -579,6 +600,33 @@ dependencies = [ "libc", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -811,6 +859,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2927,6 +3010,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opener" version = "0.8.4" @@ -2981,6 +3070,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" @@ -3147,6 +3246,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.18.1" @@ -3306,6 +3433,7 @@ name = "pwsp-gui" version = "1.12.0" dependencies = [ "anyhow", + "criterion", "eframe", "egui", "egui_dnd", @@ -4607,6 +4735,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -5429,6 +5567,22 @@ dependencies = [ "web-sys", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -5438,6 +5592,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.62.2" diff --git a/pwsp-gui/Cargo.toml b/pwsp-gui/Cargo.toml index cb708f2..c92f879 100644 --- a/pwsp-gui/Cargo.toml +++ b/pwsp-gui/Cargo.toml @@ -34,6 +34,9 @@ sys-locale.workspace = true reqwest.workspace = true percent-encoding.workspace = true +[dev-dependencies] +criterion = "0.8.2" + [package.metadata.deb] assets = [ [ @@ -67,3 +70,7 @@ assets = [ "644", ], ] + +[[bench]] +name = "bench" +harness = false diff --git a/pwsp-gui/benches/bench.rs b/pwsp-gui/benches/bench.rs new file mode 100644 index 0000000..c986a8b --- /dev/null +++ b/pwsp-gui/benches/bench.rs @@ -0,0 +1,89 @@ +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use std::path::PathBuf; + +fn simulate_loop(children: &[PathBuf], search_query: &str) -> usize { + let mut count = 0; + for child in children { + if !child.is_dir() { + let ext = child + .extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default(); + let supported = [ + "mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", + "avi", "opus", + ]; + if !supported.contains(&ext) { + continue; + } + if !search_query.is_empty() { + let file_name = child + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + if !file_name.to_lowercase().contains(search_query) { + continue; + } + } + } + count += 1; + } + count +} + +fn simulate_optimized_loop(children: &[PathBuf], search_query: &str) -> usize { + let mut count = 0; + for child in children { + if !child.is_dir() + && !search_query.is_empty() { + let file_name = child + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + if !file_name.to_lowercase().contains(search_query) { + continue; + } + } + count += 1; + } + count +} + +fn benchmark(c: &mut Criterion) { + let mut children = Vec::new(); + for i in 0..10000 { + let ext = if i % 2 == 0 { "mp3" } else { "txt" }; + children.push(PathBuf::from(format!("file_{}.{}", i, ext))); + } + let search_query = ""; + + c.bench_function("unoptimized_loop", |b| { + b.iter(|| simulate_loop(black_box(&children), black_box(search_query))) + }); + + let filtered_children: Vec<_> = children + .into_iter() + .filter(|child| { + let ext = child + .extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default(); + let supported = [ + "mp3", "wav", "ogg", "flac", "mp4", "m4a", "aac", "mov", "mkv", "mka", "webm", + "avi", "opus", + ]; + supported.contains(&ext) + }) + .collect(); + + c.bench_function("optimized_loop", |b| { + b.iter(|| simulate_optimized_loop(black_box(&filtered_children), black_box(search_query))) + }); +} + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/pwsp-gui/src/gui/views/body.rs b/pwsp-gui/src/gui/views/body.rs index ffd2077..fae49d5 100644 --- a/pwsp-gui/src/gui/views/body.rs +++ b/pwsp-gui/src/gui/views/body.rs @@ -308,7 +308,18 @@ impl SoundpadGui { let mut read = Vec::new(); if let Ok(entries) = std::fs::read_dir(&path) { for entry in entries.filter_map(|e| e.ok()) { - read.push(entry.path()); + let child_path = entry.path(); + if !child_path.is_dir() + && !crate::gui::SUPPORTED_EXTENSIONS.contains( + &child_path + .extension() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), + ) { + continue; + } + read.push(child_path); } } let sort_order = config.get_sort_order(&path); @@ -331,17 +342,8 @@ impl SoundpadGui { let search_query = search_query.trim(); for child in children { - if !child.is_dir() { - if !crate::gui::SUPPORTED_EXTENSIONS.contains( - &child - .extension() - .unwrap_or_default() - .to_str() - .unwrap_or_default(), - ) { - continue; - } - if !search_query.is_empty() { + if !child.is_dir() + && !search_query.is_empty() { let file_name = child .file_name() .unwrap_or_default() @@ -351,7 +353,6 @@ impl SoundpadGui { continue; } } - } Self::draw_tree_node(ui, child, config, app_state, audio_player_state, actions); } });