Compare commits
No commits in common. 'refactor-search-capext' and 'master' have entirely different histories.
refactor-s
...
master
@ -1,162 +0,0 @@
|
|||||||
//! 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,385 +0,0 @@
|
|||||||
//! Partitioning even areas by delimitor byte.
|
|
||||||
use super::*;
|
|
||||||
use std::{
|
|
||||||
num::NonZeroUsize,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Midpoint searcher (forwards & backwards)
|
|
||||||
trait MidpointFBSearcher<T=u8>
|
|
||||||
{
|
|
||||||
fn search_forward<'a>(&self, haystack: &'a [T], needle: T) -> Option<&'a T>;
|
|
||||||
fn search_backward<'a>(&self, haystack: &'a [T], needle: T) -> Option<&'a T>;
|
|
||||||
|
|
||||||
fn search_combined<'a>(&self, haystack: &'a [T], begin: usize, needle: T) -> Option<&'a T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search the pivot for the needle sequentially.
|
|
||||||
///
|
|
||||||
/// The order of operations will be: `search_forward()?, search_backward()`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SearchSeq;
|
|
||||||
|
|
||||||
impl MidpointFBSearcher<u8> for SearchSeq
|
|
||||||
{
|
|
||||||
#[inline(always)]
|
|
||||||
fn search_forward<'a>(&self, haystack: &'a [u8], needle: u8) -> Option<&'a u8> {
|
|
||||||
memchr::memchr(needle, haystack).map(move |i| &haystack[i])
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn search_backward<'a>(&self, haystack: &'a [u8], needle: u8) -> Option<&'a u8> {
|
|
||||||
memchr::memrchr(needle, haystack).map(move |i| &haystack[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
match haystack.split_at(begin) {
|
|
||||||
([], []) => None,
|
|
||||||
([], x) => self.search_forward(x, needle),
|
|
||||||
(x, []) => self.search_backward(x, needle),
|
|
||||||
|
|
||||||
// If both the buffers are lower than `max_cap`, just do the entire operation on each
|
|
||||||
(x, y) if max_cap.map(|max| x.len() <= max.get() && y.len() <= max.get()).unwrap_or(false) => {
|
|
||||||
self.search_forward(y, needle)?;
|
|
||||||
self.search_backward(x, needle)
|
|
||||||
},
|
|
||||||
|
|
||||||
(mut x, mut y) => {
|
|
||||||
let len = std::cmp::min(x.len(), y.len());
|
|
||||||
let mut cap = std::cmp::min(len, DEFAULT_PIVOT_MAX_SEARCH_AREA);
|
|
||||||
|
|
||||||
while cap <= len {
|
|
||||||
// If cap is too large for one (or more) of the buffers, truncate it.
|
|
||||||
if cap > y.len() || cap > x.len() {
|
|
||||||
cap = std::cmp::min(y.len(), x.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search forwards in `y`. (up to `cap`)
|
|
||||||
if let Some(y) = self.search_forward(&y[..cap], needle) {
|
|
||||||
return Some(y);
|
|
||||||
}
|
|
||||||
// Search backwards in `x`. (down to `cap`)
|
|
||||||
if let Some(x) = self.search_backward(&x[(x.len()-cap)..], needle) {
|
|
||||||
return Some(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cut out `cap` bytes from the start of forwards
|
|
||||||
y = &y[cap..];
|
|
||||||
// Cut out `cap` bytes from the end of backwards.
|
|
||||||
x = &x[..cap];
|
|
||||||
// Grow `cap` by 1 ^2 (not passing `max_cap` if there is one set.)
|
|
||||||
cap = max_cap.map(|max| std::cmp::min(max.get(), cap << 1)).unwrap_or_else(|| cap << 1);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature="async")]
|
|
||||||
const _TODO_FUTURES_JOIN2_ASYNC_SEARCH: () = {
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct SearchAsync<F>
|
|
||||||
{
|
|
||||||
spawn_task: F,
|
|
||||||
result: oneshot::Receiver<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="threads-async")]
|
|
||||||
impl<F, Fu> MidpointFBSearcher<u8> for SearchAsync<F>
|
|
||||||
where F: Fn() -> Fu,
|
|
||||||
Fu: futures::Future + Send + Sync + 'static
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/// Search in parallel.
|
|
||||||
///
|
|
||||||
/// # Warning
|
|
||||||
/// This search operation is heavy. It **always** spawns its own (up to) two threads when `search_combined()` is invoked. This may not be ideal...
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
struct SearchPar
|
|
||||||
{
|
|
||||||
cap_start: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For f/b pivot-searching, the max area for each operation to attempt.
|
|
||||||
const DEFAULT_PIVOT_MAX_SEARCH_AREA: usize = 1024;
|
|
||||||
/// For f/b pivot-searching, the max *possible* area for each operation to attempt when it grows in capacity
|
|
||||||
const DEFAULT_PIVOT_MAX_POSSIBLE_SEARCH_AREA: usize = (1024 * 1024 * 1024) * 2; // 2GB
|
|
||||||
|
|
||||||
/// The number of pages of memory loaded where non-page-bound operations assume they're using HP-mapped data.
|
|
||||||
const DEFAULT_MEM_DETECT_HUGE_SIZE_PAGES: usize = 4;
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
/// Real system page size.
|
|
||||||
static ref REAL_PAGE_SIZE: std::ffi::c_int = {
|
|
||||||
use std::ffi::c_int;
|
|
||||||
extern "C" {
|
|
||||||
fn getpagesize() -> c_int;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
getpagesize()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// Get a recommended bound for the pivot search area (if there is one.)
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// The recommended max bound for a pivot search area, or `None` for unbounded search.
|
|
||||||
///
|
|
||||||
/// # Page kinds
|
|
||||||
/// If the operation is using huge-page mapped memory, set `use_hp` to true.
|
|
||||||
#[inline]
|
|
||||||
fn get_max_pivot_search_area(_use_hp: bool) -> Option<NonZeroUsize>
|
|
||||||
{
|
|
||||||
use std::ffi::c_int;
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref PAGE_SIZE: usize = {
|
|
||||||
match *REAL_PAGE_SIZE {
|
|
||||||
c_int::MIN..=0 => DEFAULT_PIVOT_MAX_SEARCH_AREA,
|
|
||||||
// Very large (hp)
|
|
||||||
very_large if very_large as usize > DEFAULT_PIVOT_MAX_POSSIBLE_SEARCH_AREA => 0,
|
|
||||||
// Large (limit to upper bound of non-hp)
|
|
||||||
large if large as usize >= DEFAULT_PIVOT_MAX_SEARCH_AREA => std::cmp::min(large as usize, DEFAULT_PIVOT_MAX_POSSIBLE_SEARCH_AREA),
|
|
||||||
// Smaller than default bound
|
|
||||||
small => small as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//XXX: Should we return a different value if `use_hp` is enabled? ("using hugepage")
|
|
||||||
NonZeroUsize::new(*PAGE_SIZE)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SearchPar {
|
|
||||||
#[inline(always)]
|
|
||||||
pub const fn new() -> Self
|
|
||||||
{
|
|
||||||
Self::with_capacity(DEFAULT_PIVOT_MAX_SEARCH_AREA)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub const fn with_capacity(cap_start: usize) -> Self
|
|
||||||
{
|
|
||||||
Self { cap_start }
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub const fn cap(&self) -> usize
|
|
||||||
{
|
|
||||||
self.cap_start
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub unsafe fn cap_mut(&mut self) -> &mut usize
|
|
||||||
{
|
|
||||||
&mut self.cap_start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SearchPar
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
cap_start: match *REAL_PAGE_SIZE {
|
|
||||||
std::ffi::c_int::MIN..=0 => DEFAULT_PIVOT_MAX_SEARCH_AREA,
|
|
||||||
above_zero => above_zero as usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="threads")]
|
|
||||||
impl MidpointFBSearcher<u8> for SearchPar
|
|
||||||
{
|
|
||||||
#[inline(always)]
|
|
||||||
fn search_forward<'a>(&self, haystack: &'a [u8], needle: u8) -> Option<&'a u8> {
|
|
||||||
memchr::memchr(needle, haystack).map(move |i| &haystack[i])
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
fn search_backward<'a>(&self, haystack: &'a [u8], needle: u8) -> Option<&'a u8> {
|
|
||||||
memchr::memrchr(needle, haystack).map(move |i| &haystack[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_combined<'a>(&self, haystack: &'a [u8], begin: usize, needle: u8) -> Option<&'a u8> {
|
|
||||||
|
|
||||||
let complete = crossbeam::atomic::AtomicCell::new(false);
|
|
||||||
std::thread::scope(|s| {
|
|
||||||
//let mut complete_val = UnsafeCell::new(false);
|
|
||||||
//let complete: parking_lot::Once = parking_lot::Once::new();
|
|
||||||
// let value_cont = (parking_lot::Condvar::new(), parking_lot::FairMutex::new(None::<&'a u8>));
|
|
||||||
|
|
||||||
let (mut hb, mut hf) = haystack.split_at(begin);
|
|
||||||
|
|
||||||
let max_cap = match get_max_pivot_search_area(hf.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,
|
|
||||||
};
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
let forward = if hf.len() > 0 {
|
|
||||||
let cap = cap;
|
|
||||||
let sf = &self;
|
|
||||||
let complete = &complete;
|
|
||||||
// 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);
|
|
||||||
return Some(x);
|
|
||||||
} else if complete.load() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Cut out `cap` bytes from the start.
|
|
||||||
hf = &hf[cap..];
|
|
||||||
// Grow `cap` by 1 ^2 (not passing `max_cap` if there is one set.)
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
return Some(x);
|
|
||||||
} else if complete.load() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Cut out `cap` bytes from the end.
|
|
||||||
hb = &hb[..cap];
|
|
||||||
// Grow `cap` by 1 ^2 (not passing `max_cap` if there is one set.)
|
|
||||||
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, back @ Some(_)) => back,
|
|
||||||
(Some(forward), backward) => backward.or_else(move || forward.join().unwrap_or_panic(_resume_unwind)),
|
|
||||||
//(Some(forward), Some(_)) => Handled ^
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn partition_once_with<'a, S>(buffer: &'a [u8], needle: u8, method: S) -> (&'a [u8], &'a [u8])
|
|
||||||
where S: MidpointFBSearcher<u8>
|
|
||||||
{
|
|
||||||
todo!("Perform one single buffer partition partition (buffer/2.at_nearest_mpr(needle)) (using `method.search_combined()`) and return its parts. If we can fast-path skip the `search_combined()` then that is okay (e.g. if the buffer/2 is small enough that we should just use `SearchSeq`, we can use `SearchSeq` instead of `S`, and so on.) (XXX: Also see below about thread spawning on parallelised partitions and re-using thread pools (we may be able to do this manually with crossbeam, or we might just have to embrace using `spawn_blocking()` async/a default tokio multithreaded-runtime) since parallel partitions needs at least two threads to search both directions at a time.)")
|
|
||||||
}
|
|
||||||
|
|
||||||
//XXX: Should we add a `SearchAsync`? Or an impl for SearchPar that uses already-spawned threads? TODO: It would be best if we could re-use extant threads instead of spawning two on each partition...
|
|
||||||
|
|
||||||
//Parallel (feature="threads") byte area partition-on-nearest-newline-to-halve impl, and non-parallel (default) impl. These impls can differ in their desired depths of partitioning (using parallel impls should balance num of partitions to num of logical cpus & input(/desired chunk) size.)
|
|
||||||
|
|
||||||
//TODO: Add tests for `Search{Seq,Par}` partitioning methods.
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test
|
|
||||||
{
|
|
||||||
#[test]
|
|
||||||
fn partition_seq()
|
|
||||||
{
|
|
||||||
todo!("Test `SearchSeq` sequential partition searcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="threads")]
|
|
||||||
#[test]
|
|
||||||
fn partition_par_heavy()
|
|
||||||
{
|
|
||||||
todo!("Test `SearchPar` parallel partition searcher")
|
|
||||||
}
|
|
||||||
|
|
||||||
//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(feature="threads-async")]
|
|
||||||
#[/*tokio::*/test]
|
|
||||||
fn partition_par_async()
|
|
||||||
{
|
|
||||||
unimplemented!("A pure async parallel searcher has not yet been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in new issue