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.

Main thread yields once before starting its search loop.

Fortune for reverse's current commit: Curse − 凶
refactor-search-capext
Avril 1 month ago
parent 9d2ae29e8b
commit 49e0dd6073
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -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<T>` *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::<Self>())
} 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<T: ?Sized + Unreachable> Unreachable for Box<T> {}
pub trait UnwrapPanicExt<T, E> {
/// 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: Unreachable, F: FnOnce(E) -> 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<F: FnOnce(E) -> Never>(self, func: F) -> T
where Self: Sized {
self.unwrap_or_panic::<Never, _>(func)
}
}
pub trait UnwrapInfallibleExt<T> {
/// Unwrapping is infallible and therefore safe to do so without checking.
fn unwrap_infallible(self) -> T;
}
pub trait UnwrapPanicResumeExt<T> {
/// Unwrap or resume a previous unwind, with the unwind payload in the `Err` variant.
fn unwrap_or_resume(self) -> T;
}
impl<T, E> UnwrapPanicExt<T, E> for Result<T, E>
{
#[inline]
fn unwrap_or_panic<N: Unreachable, F: FnOnce(E) -> N>(self, func: F) -> T {
#[inline(never)]
#[cold]
fn _do_panic<Nn: Unreachable, Ee, Ff: FnOnce(Ee) -> Nn>(error: Ee, func: Ff) -> !
{
func(error).into_unreachable()
}
match self {
Ok(v) => v,
Err(e) => _do_panic(e, func)
}
}
}
impl<T, E: Unreachable> UnwrapInfallibleExt<T> for Result<T, E>
{
#[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<dyn std::any::Any + Send>;
#[cold]
#[inline(never)]
fn _resume_unwind<E: Into<UnwindPayload>>(e: E) -> !
{
std::panic::resume_unwind(e.into())
}
impl<T, E: Into<UnwindPayload>> UnwrapPanicResumeExt<T> for Result<T, E>
{
#[inline]
fn unwrap_or_resume(self) -> T {
match self {
Ok(v) => v,
Err(e) => _resume_unwind(e),
}
}
}

@ -1,4 +1,7 @@
#![cfg_attr(feature="unstable", feature(never_type))] // See `Never`.
#[macro_use] mod ext; use ext::*;
mod part;
//#[inline]

@ -33,13 +33,13 @@ impl MidpointFBSearcher<u8> 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<u8> 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<u8> 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<u8> 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<dyn std::any::Any + Send>) -> 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")
}
}

Loading…
Cancel
Save