use super::*;
pub fn scan_lines_parallel<T, F>(bytes: &[u8], visit: F) -> (Vec<T>, usize)
where
F: Fn(&[u8], usize) -> LineVerdict<T> + Sync,
T: Send,
{
const MIN_PARALLEL_BYTES: usize = 4 * 1024 * 1024;
let target_chunks = if bytes.len() < MIN_PARALLEL_BYTES {
1
} else {
(rayon::current_num_threads() * 4).max(1)
};
scan_lines_parallel_chunked(bytes, &visit, target_chunks)
}
pub fn parse_candidates_parallel<F>(bytes: &[u8], prefilter: F) -> (Vec<(usize, Record)>, usize)
where
F: Fn(&[u8]) -> bool + Sync,
{
scan_lines_parallel(bytes, |line, line_no| {
if !prefilter(line) {
return non_candidate_verdict(line);
}
match parse_line(line) {
Ok(Some(rec)) => LineVerdict::Keep((line_no, rec)),
Ok(None) => LineVerdict::Ignore,
Err(_) => LineVerdict::Skip,
}
})
}
pub type NumberedRecords = Vec<(usize, Record)>;
pub fn parse_candidates_with_spine<F>(
bytes: &[u8],
prefilter: F,
) -> (NumberedRecords, Vec<SpineRow>, usize)
where
F: Fn(&[u8]) -> bool + Sync,
{
scan_lines_parallel_split(bytes, |line, line_no| {
if !prefilter(line) {
if let Some(row) = spine_record(line_no, line) {
return SplitVerdict::Second(row);
}
return non_candidate_split(line);
}
match parse_line(line) {
Ok(Some(rec)) => SplitVerdict::First((line_no, rec)),
Ok(None) => SplitVerdict::Ignore,
Err(_) => SplitVerdict::Skip,
}
})
}
#[must_use]
pub fn non_candidate_verdict<T>(line: &[u8]) -> LineVerdict<T> {
if line_shape_malformed(line) {
LineVerdict::Skip
} else {
LineVerdict::Ignore
}
}
#[must_use]
pub fn non_candidate_split<A, B>(line: &[u8]) -> SplitVerdict<A, B> {
if line_shape_malformed(line) {
SplitVerdict::Skip
} else {
SplitVerdict::Ignore
}
}
#[must_use]
pub fn line_shape_malformed(line: &[u8]) -> bool {
let t = line.trim_ascii();
!t.is_empty() && (t[0] != b'{' || t[t.len() - 1] != b'}')
}
#[derive(Debug)]
pub enum SplitVerdict<A, B> {
First(A),
Second(B),
Skip,
Ignore,
}
pub fn scan_lines_parallel_split<A, B, F>(bytes: &[u8], visit: F) -> (Vec<A>, Vec<B>, usize)
where
F: Fn(&[u8], usize) -> SplitVerdict<A, B> + Sync,
A: Send,
B: Send,
{
const MIN_PARALLEL_BYTES: usize = 4 * 1024 * 1024;
let target_chunks = if bytes.len() < MIN_PARALLEL_BYTES {
1
} else {
(rayon::current_num_threads() * 4).max(1)
};
scan_lines_parallel_split_chunked(bytes, &visit, target_chunks)
}
pub(crate) fn scan_lines_parallel_chunked<T, F>(
bytes: &[u8],
visit: &F,
target_chunks: usize,
) -> (Vec<T>, usize)
where
F: Fn(&[u8], usize) -> LineVerdict<T> + Sync,
T: Send,
{
let (kept, _empty, skipped): (Vec<T>, Vec<std::convert::Infallible>, usize) =
scan_lines_parallel_split_chunked(
bytes,
&|line: &[u8], line_no: usize| match visit(line, line_no) {
LineVerdict::Keep(t) => SplitVerdict::First(t),
LineVerdict::Skip => SplitVerdict::Skip,
LineVerdict::Ignore => SplitVerdict::Ignore,
},
target_chunks,
);
(kept, skipped)
}
pub(crate) fn scan_lines_parallel_split_chunked<A, B, F>(
bytes: &[u8],
visit: &F,
target_chunks: usize,
) -> (Vec<A>, Vec<B>, usize)
where
F: Fn(&[u8], usize) -> SplitVerdict<A, B> + Sync,
A: Send,
B: Send,
{
if bytes.is_empty() {
return (Vec::new(), Vec::new(), 0);
}
let target_chunks = target_chunks.max(1);
let approx = (bytes.len() / target_chunks).max(1);
let mut bounds = vec![0usize];
let mut pos = approx;
while pos < bytes.len() {
match memchr(b'\n', &bytes[pos..]) {
Some(off) => {
let b = pos + off + 1;
if b >= bytes.len() {
break;
}
bounds.push(b);
pos = b + approx;
}
None => break,
}
}
bounds.push(bytes.len());
let chunks: Vec<(usize, usize)> = bounds.windows(2).map(|w| (w[0], w[1])).collect();
let mut start_line = Vec::with_capacity(chunks.len());
start_line.push(1usize);
if chunks.len() > 1 {
let nl_per_chunk: Vec<usize> = chunks[..chunks.len() - 1]
.par_iter()
.map(|&(s, e)| memchr_iter(b'\n', &bytes[s..e]).count())
.collect();
let mut acc = 1usize;
for &n in &nl_per_chunk {
acc += n;
start_line.push(acc);
}
}
let per_chunk: Vec<(Vec<A>, Vec<B>, usize)> = chunks
.par_iter()
.enumerate()
.map(|(ci, &(s, e))| {
let base = start_line[ci];
let mut first: Vec<A> = Vec::new();
let mut second: Vec<B> = Vec::new();
let mut skipped = 0usize;
let mut j = 0usize;
let _ = scan_lines_bytes(&bytes[s..e], |line| {
let line_no = base + j;
j += 1;
match visit(line, line_no) {
SplitVerdict::First(t) => first.push(t),
SplitVerdict::Second(t) => second.push(t),
SplitVerdict::Skip => skipped += 1,
SplitVerdict::Ignore => {}
}
});
(first, second, skipped)
})
.collect();
let mut all_first: Vec<A> = Vec::new();
let mut all_second: Vec<B> = Vec::new();
let mut skipped_total = 0usize;
for (first, second, sk) in per_chunk {
all_first.extend(first);
all_second.extend(second);
skipped_total += sk;
}
(all_first, all_second, skipped_total)
}