From 49e0dd6073fd1f942941605475be57c26f106e58 Mon Sep 17 00:00:00 2001 From: Avril Date: Wed, 3 Apr 2024 00:12:01 +0100 Subject: [PATCH] part: Removed unneeded 2nd thread spawn in `SearchPar`"s `search_combined()`: Backwards searching is done on main thread, forward searching is done on background thread. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main thread yields once before starting its search loop. Fortune for reverse's current commit: Curse − 凶 --- src/ext.rs | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 + src/part.rs | 97 ++++++++++++++++++++----------- 3 files changed, 229 insertions(+), 33 deletions(-) create mode 100644 src/ext.rs diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..4f4cfe2 --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,162 @@ +//! Extension traits, global functions + macros. +#![allow(unused)] +use super::*; +use std::convert::Infallible; + +/// The default bottom type. +/// +/// To use the `unwrap_infallible()`-like interface, functions that return `-> !` should be changed to `-> Never`. +/// When `unstable` is enabled, this is an alias to `!` and `-> !` is not special cased. +/// +/// # As return argument +/// When feature `unstable` is enabled, `into_unreachable()` may not be required to ensure propogation to `!` from a function returning `-> Never`. +#[cfg(feature="unstable")] +pub type Never = !; + +/// The default bottom type. +/// +/// To use the `unwrap_infallible()`-like interface, functions special cased to `-> !` should be changed to `-> Never`. +/// +/// # As return argument +/// When feature `unstable` is not enabled, `into_unreachable()` may be required to be used when dealing with return bottom types other than the special case `-> !`. +/// This is a current limitation of the type system. +#[cfg(not(feature="unstable"))] +pub type Never = Infallible; + +/// Contractually ensures this type cannot exist (i.e. it is a bottom type.) +/// +/// # Safety +/// Instances of the impl type **cannot exist**. +/// They must be bottom types (i.e. empty enums, types contatining an `Infallible` / `!` object, etc.) +/// +/// # Auto-impl +/// This trait is not intended to be implemented on any user-defined type other than empty enums. +/// +/// By default it is implemented for the following types: +/// - `core::convert::Infallible` +/// - `!` (**feature**: `unstable`) +/// - `Box` *where* `T: ?Sized + Unreachable` +pub unsafe trait Unreachable { + /// Force control flow to terminate type checking here. + /// + /// # Note + /// This function will never be executed, it is used to terminate the value's existence in the type system, by converting it from any `Unreachable` type into the bottom return type `!`. + /// If this function ever **can** be called at all, it is undefined behaviour. + #[inline] + #[cold] + fn into_unreachable(self) -> ! + where Self: Sized + { + if cfg!(debug_assertions) { + unreachable!("Unreachable conversion from {}!", std::any::type_name::()) + } else { + // SAFETY: Contractually enforced by the trait impl itself. + unsafe { + std::hint::unreachable_unchecked() + } + } + } +} + +unsafe impl Unreachable for Infallible { + #[inline(always)] + #[cold] + fn into_unreachable(self) -> ! { + match self {} + } +} +#[cfg(feature="unstable")] +unsafe impl Unreachable for ! { + #[inline(always)] + #[cold] + fn into_unreachable(self) -> ! { + match self {} + } +} + +unsafe impl Unreachable for Box {} + +pub trait UnwrapPanicExt { + /// Unwrap the result `Ok` value or panic as described by the non-returning function `F`, with the `Unreachable` bottom type `N`. + /// This will usually be an `-> !` function, (or an `-> Never` function using the `Unreachable` interface.) + /// + /// # Panic usage + /// `func` must not return. It should panic, resume a panic, or exit the thread/program, trap, or terminate in an infinite loop. + /// + /// It does not *have* to call `panic!()` if it terminates in another way that is not a panic, however. + fn unwrap_or_panic N>(self, func: F) -> T; + + /// Unwrap the result `Ok` value or panic as described by the non-returning function `func` with the default bottom type `Never`. + #[inline(always)] + fn unwrap_or_panic_unreachable Never>(self, func: F) -> T + where Self: Sized { + self.unwrap_or_panic::(func) + } +} + +pub trait UnwrapInfallibleExt { + /// Unwrapping is infallible and therefore safe to do so without checking. + fn unwrap_infallible(self) -> T; +} + +pub trait UnwrapPanicResumeExt { + /// Unwrap or resume a previous unwind, with the unwind payload in the `Err` variant. + fn unwrap_or_resume(self) -> T; +} + +impl UnwrapPanicExt for Result +{ + #[inline] + fn unwrap_or_panic N>(self, func: F) -> T { + #[inline(never)] + #[cold] + fn _do_panic Nn>(error: Ee, func: Ff) -> ! + { + func(error).into_unreachable() + } + + match self { + Ok(v) => v, + Err(e) => _do_panic(e, func) + } + } +} + +impl UnwrapInfallibleExt for Result +{ + #[inline] + fn unwrap_infallible(self) -> T { + match self { + Ok(v) => v, + Err(e) => if cfg!(debug_assertions) { + e.into_unreachable() + } else { + // SAFETY: Contract bound of `E: Unreachable` ensures this path will never be taken. + unsafe { + std::hint::unreachable_unchecked() + } + } + } + } +} + +/// The type of a caught unwind payload. +pub type UnwindPayload = Box; + +#[cold] +#[inline(never)] +fn _resume_unwind>(e: E) -> ! +{ + std::panic::resume_unwind(e.into()) +} + +impl> UnwrapPanicResumeExt for Result +{ + #[inline] + fn unwrap_or_resume(self) -> T { + match self { + Ok(v) => v, + Err(e) => _resume_unwind(e), + } + } +} diff --git a/src/main.rs b/src/main.rs index 144838a..914b3e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ +#![cfg_attr(feature="unstable", feature(never_type))] // See `Never`. + +#[macro_use] mod ext; use ext::*; mod part; //#[inline] diff --git a/src/part.rs b/src/part.rs index 9e30f57..b4c4cc3 100644 --- a/src/part.rs +++ b/src/part.rs @@ -33,13 +33,13 @@ impl MidpointFBSearcher for SearchSeq #[inline] fn search_combined<'a>(&self, haystack: &'a [u8], begin: usize, needle: u8) -> Option<&'a u8> { let max_cap = match get_max_pivot_search_area(haystack.len() > (DEFAULT_PIVOT_MAX_SEARCH_AREA * DEFAULT_MEM_DETECT_HUGE_SIZE_PAGES)){ // Assume huge-page memory if len is larger than 4 pages. - - // On debug builds, cap search area to one system page only. - _ignore if cfg!(debug_assertions) && (*REAL_PAGE_SIZE) > 0 => - // SAFETY: We have checked if `*REAL_PAGE_SIZE` is non-zero above. - Some(unsafe { NonZeroUsize::new_unchecked(*REAL_PAGE_SIZE as usize) }), - // Otherwise, use the detected value. - cap => cap, + + // On debug builds, cap search area to one system page only. + _ignore if cfg!(debug_assertions) && (*REAL_PAGE_SIZE) > 0 => + // SAFETY: We have checked if `*REAL_PAGE_SIZE` is non-zero above. + Some(unsafe { NonZeroUsize::new_unchecked(*REAL_PAGE_SIZE as usize) }), + // Otherwise, use the detected value. + cap => cap, }; @@ -236,25 +236,31 @@ impl MidpointFBSearcher for SearchPar }; // Cap the cap to `max_cap` if there is a max cap. let cap = if let Some(max) = max_cap.as_ref() { - std::cmp::min(max.get(), self.cap_start) - } else { - self.cap_start - }; + std::cmp::min(max.get(), self.cap_start) + } else { + self.cap_start + }; let forward = if hf.len() > 0 { let cap = cap; let sf = &self; let complete = &complete; - Some(s.spawn(move || -> Option<_> { + // Background thread: Forward search (`forward-searcher`.) + Some(std::thread::Builder::new().name("forward-searcher".into()).spawn_scoped(s, move || -> Option<_> { let mut cap = std::cmp::min(cap, hf.len()); let len = hf.len(); + + // Check completion before starting loop too. + if complete.load() { + return None; + } while cap <= len { // If `cap` is larger than the buffer `hf`, truncate it. cap = std::cmp::min(cap, hf.len()); // Search forward in `hf` up to `cap` bytes. if let /*v @ */Some(x) = sf.search_forward(&hf[..cap], needle) { + // Tell other operation we have found something. complete.store(true); - //complete.call_once(|| complete_val = true); return Some(x); } else if complete.load() { break; @@ -265,24 +271,34 @@ impl MidpointFBSearcher for SearchPar cap = max_cap.map(|max| std::cmp::min(max.get(), cap << 1)).unwrap_or_else(|| cap << 1); } None::<&'a u8> - })) + }).expect("Failed to spawn forward-searcher thread")) } else { None }; + //NOTE: There is no need to spawn another thread for the 2nd operation, since they are both join()'d at the end regardless and both already communicate completion. let backward = if hb.len() > 0 { let cap = cap; let sf = &self; let complete = &complete; - Some(s.spawn(move || -> Option<_> { + + // Main thread: Backwards search. + move || -> Option<_> { let mut cap = std::cmp::min(cap, hb.len()); let len = hb.len(); + + // Check completion before starting loop too. + if complete.load() { + return None; + } else { + // Allow previous thread to run if it is not. + std::thread::yield_now(); + } while cap <= len { // If `cap` is larger than the buffer `hb`, truncate it. cap = std::cmp::min(cap, hb.len()); // Search backwards in `hb` up to `cap` bytes. if let /*v @ */Some(x) = sf.search_backward(&hb[(hb.len()-cap)..], needle) { complete.store(true); - //complete.call_once(|| complete_val = true); return Some(x); } else if complete.load() { break; @@ -293,17 +309,32 @@ impl MidpointFBSearcher for SearchPar cap = max_cap.map(|max| std::cmp::min(max.get(), cap << 1)).unwrap_or_else(|| cap << 1); } None::<&'a u8> - })) + }() } else { None }; + if backward.is_some() && forward.as_ref().map(|th| !th.is_finished()).unwrap_or(false) { + // `backward` found something, `forward` is still running. + debug_assert_ne!(complete.load(), false, "Complete has not been set! (main thread waiting for forward-searcher thread"); + complete.store(true); + } + + #[cold] + #[inline(never)] + fn _resume_unwind(e: Box) -> Never + { + if cfg!(debug_assertions) { + panic!("forward-searcher thread panic") + } else { + std::panic::resume_unwind(e) + } + } match (forward, backward) { (None, None) => None, - (None, Some(back)) => back.join().unwrap_or(None), - (Some(forward), None) => forward.join().unwrap_or(None), - (Some(forward), Some(backward)) => forward.join().unwrap_or(None). - or_else(move || backward.join().unwrap_or(None)), + (None, back @ Some(_)) => back, + (Some(forward), backward) => backward.or_else(move || forward.join().unwrap_or_panic(_resume_unwind)), + //(Some(forward), Some(_)) => Handled ^ } }) @@ -338,17 +369,17 @@ mod test } //TODO: Thread-reusing parallel `MidpointFBSearcher` (SearchSeq is thread-*spawning*; heavy.) This may require we use async and tasks. If it does, we should also create a `SearchAsync` partitioner (XXX: MidpointFBSearcher is currently a synchonous-only interface; a pure-async pivot finder may require a refactor.) - #[cfg(all(feature="threads-async", feature = "threads"))] - #[test] - fn partition_par_light() - { - unimplemented!("A light (thread-*reusing*) parallel searcher has not yet been implemented") - } + #[cfg(all(feature="threads-async", feature = "threads"))] + #[test] + fn partition_par_light() + { + unimplemented!("A light (thread-*reusing*) parallel searcher has not yet been implemented") + } - #[cfg(feature="threads-async")] - #[/*tokio::*/test] - fn partition_par_async() - { - unimplemented!("A pure async parallel searcher has not yet been implemented") - } + #[cfg(feature="threads-async")] + #[/*tokio::*/test] + fn partition_par_async() + { + unimplemented!("A pure async parallel searcher has not yet been implemented") + } }