From 9d118de05c8cbd8bd358032617b58f5fe60d26b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:27:29 +0000 Subject: [PATCH 1/2] Initial plan From c01060d97a220e59a9e2cbf8867a038fdeb45de7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 02:38:20 +0000 Subject: [PATCH 2/2] perf: optimize do_with_filter for no-op case - Add #[inline] to do_with_filter to enable better optimization - Split slow path into separate #[cold] function to optimize branch prediction - The common case (no filter) now has minimal overhead with better inlining Co-authored-by: reubeno <10508433+reubeno@users.noreply.github.com> --- brush-core/src/filter.rs | 54 ++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/brush-core/src/filter.rs b/brush-core/src/filter.rs index bdc38bb7f..23a2a675a 100644 --- a/brush-core/src/filter.rs +++ b/brush-core/src/filter.rs @@ -58,12 +58,46 @@ pub trait OpFilter: Send { } } +/// Executes an operation with a filter. +/// +/// This is the slow path, marked as `#[cold]` to optimize for the common case +/// where no filter is present. +#[cold] +async fn do_with_filter_slow( + input: O::Input, + filter: &Arc>, + executor: Exec, +) -> O::Output +where + O: FilterableOp, + F: OpFilter + ?Sized, + Exec: FnOnce(O::Input) -> Fut, + Fut: Future + Send, +{ + let mut filter_guard = filter.lock().await; + let pre_op_result = filter_guard.pre_op(input); + drop(filter_guard); + + let output = match pre_op_result { + PreFilterResult::Continue(input) => executor(input).await, + PreFilterResult::Return(output) => output, + }; + + let mut filter_guard = filter.lock().await; + match filter_guard.post_op(output) { + PostFilterResult::Return(output) => output, + } +} + /// Executes an operation with the provided inputs, applying a filter if present. /// /// The filter can inspect and modify inputs before execution, and inspect and /// modify outputs after execution. If no filter is provided, the executor is /// called directly. /// +/// This function is marked `#[inline]` to ensure the compiler can optimize +/// the no-filter fast path when filters are not used. +/// /// # Arguments /// /// * `input` - The inputs to the operation. @@ -76,6 +110,7 @@ pub trait OpFilter: Send { /// * `F` - The filter type. /// * `Exec` - The executor closure type. /// * `Fut` - The future type returned by the executor. +#[inline] pub async fn do_with_filter( input: O::Input, filter: &Option>>, @@ -87,21 +122,8 @@ where Exec: FnOnce(O::Input) -> Fut, Fut: Future + Send, { - let Some(filter) = filter else { - return executor(input).await; - }; - - let mut filter_guard = filter.lock().await; - let pre_op_result = filter_guard.pre_op(input); - drop(filter_guard); - - let output = match pre_op_result { - PreFilterResult::Continue(input) => executor(input).await, - PreFilterResult::Return(output) => output, - }; - - let mut filter_guard = filter.lock().await; - match filter_guard.post_op(output) { - PostFilterResult::Return(output) => output, + match filter { + None => executor(input).await, + Some(filter) => do_with_filter_slow::(input, filter, executor).await, } }