use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::sync::Arc;
use memmap2::Mmap;
use rayon::prelude::*;
use crate::common::io_error_msg;
use super::compare::{
compare_with_opts, human_numeric_to_sortable_u64, int_to_sortable_u64, parse_general_numeric,
parse_numeric_value, select_comparator, skip_leading_blanks, try_parse_integer,
};
use super::key::{KeyDef, KeyOpts, extract_key_z};
enum FileData {
Mmap(Mmap),
Owned(Vec<u8>),
}
impl std::ops::Deref for FileData {
type Target = [u8];
fn deref(&self) -> &[u8] {
match self {
FileData::Mmap(m) => m,
FileData::Owned(v) => v,
}
}
}
enum SortOutput {
Stdout(BufWriter<File>),
File(BufWriter<File>),
}
impl SortOutput {
fn stdout() -> Self {
#[cfg(unix)]
{
use std::os::unix::io::FromRawFd;
let file = unsafe { File::from_raw_fd(1) };
SortOutput::Stdout(BufWriter::with_capacity(OUTPUT_BUF_SIZE, file))
}
#[cfg(not(unix))]
{
let path = if cfg!(windows) { "CON" } else { "/dev/stdout" };
SortOutput::Stdout(BufWriter::with_capacity(
OUTPUT_BUF_SIZE,
File::open(path).unwrap_or_else(|_| panic!("Cannot open stdout")),
))
}
}
}
impl SortOutput {
#[inline]
fn write_all_direct(&mut self, buf: &[u8]) -> io::Result<()> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => {
w.flush()?;
w.get_mut().write_all(buf)
}
}
}
#[inline]
fn write_vectored_direct(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => {
w.flush()?;
w.get_mut().write_vectored(bufs)
}
}
}
}
impl Write for SortOutput {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => w.write(buf),
}
}
#[inline]
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => w.write_all(buf),
}
}
#[inline]
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => w.write_vectored(bufs),
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match self {
SortOutput::Stdout(w) | SortOutput::File(w) => w.flush(),
}
}
}
const OUTPUT_BUF_SIZE: usize = 16 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct SortConfig {
pub keys: Vec<KeyDef>,
pub separator: Option<u8>,
pub global_opts: KeyOpts,
pub unique: bool,
pub stable: bool,
pub reverse: bool,
pub check: CheckMode,
pub merge: bool,
pub output_file: Option<String>,
pub zero_terminated: bool,
pub parallel: Option<usize>,
pub buffer_size: Option<usize>,
pub temp_dir: Option<String>,
pub random_seed: u64,
pub debug: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CheckMode {
None,
Diagnose,
Quiet,
}
impl Default for SortConfig {
fn default() -> Self {
SortConfig {
keys: Vec::new(),
separator: None,
global_opts: KeyOpts::default(),
unique: false,
stable: false,
reverse: false,
check: CheckMode::None,
merge: false,
output_file: None,
zero_terminated: false,
parallel: None,
buffer_size: None,
temp_dir: None,
random_seed: 0,
debug: false,
}
}
}
#[inline]
pub fn compare_lines(a: &[u8], b: &[u8], config: &SortConfig) -> Ordering {
compare_lines_inner(a, b, config, false)
}
#[inline]
pub fn compare_lines_for_dedup(a: &[u8], b: &[u8], config: &SortConfig) -> Ordering {
compare_lines_inner(a, b, config, true)
}
#[inline]
fn compare_lines_inner(
a: &[u8],
b: &[u8],
config: &SortConfig,
skip_last_resort: bool,
) -> Ordering {
let stable = config.stable || skip_last_resort;
if !config.keys.is_empty() {
for key in &config.keys {
let opts = if key.opts.has_sort_type()
|| key.opts.ignore_case
|| key.opts.dictionary_order
|| key.opts.ignore_nonprinting
|| key.opts.ignore_leading_blanks
|| key.opts.reverse
{
&key.opts
} else {
&config.global_opts
};
let ka = extract_key_z(
a,
key,
config.separator,
opts.ignore_leading_blanks,
config.zero_terminated,
);
let kb = extract_key_z(
b,
key,
config.separator,
opts.ignore_leading_blanks,
config.zero_terminated,
);
let result = compare_with_opts(ka, kb, opts, config.random_seed);
if result != Ordering::Equal {
return result;
}
}
if !stable {
return a.cmp(b);
}
Ordering::Equal
} else {
let result = compare_with_opts(a, b, &config.global_opts, config.random_seed);
if result == Ordering::Equal && !stable {
a.cmp(b)
} else {
result
}
}
}
#[allow(clippy::type_complexity)]
fn find_lines_parallel(data: &[u8], delimiter: u8) -> (Vec<(usize, usize)>, bool) {
let num_threads = rayon::current_num_threads().max(1);
let chunk_size = data.len() / num_threads;
let mut boundaries = Vec::with_capacity(num_threads + 1);
boundaries.push(0usize);
for i in 1..num_threads {
let target = i * chunk_size;
if target >= data.len() {
break;
}
if let Some(p) = memchr::memchr(delimiter, &data[target..]) {
let boundary = target + p + 1;
if boundary > *boundaries.last().unwrap() && boundary <= data.len() {
boundaries.push(boundary);
}
}
}
boundaries.push(data.len());
let is_newline = delimiter == b'\n';
let data_len = data.len();
let data_addr = data.as_ptr() as usize;
let chunk_results: Vec<(Vec<(usize, usize)>, bool)> = boundaries
.windows(2)
.collect::<Vec<_>>()
.par_iter()
.map(|w| {
let chunk_start = w[0];
let chunk_end = w[1];
let dp = data_addr as *const u8;
let chunk =
unsafe { std::slice::from_raw_parts(dp.add(chunk_start), chunk_end - chunk_start) };
let mut offsets = Vec::with_capacity(chunk.len() / 40 + 1);
let mut line_start = chunk_start;
let mut found_cr = false;
for pos in memchr::memchr_iter(delimiter, chunk) {
let abs_pos = chunk_start + pos;
let mut line_end = abs_pos;
if is_newline && line_end > line_start && unsafe { *dp.add(line_end - 1) } == b'\r'
{
line_end -= 1;
found_cr = true;
}
offsets.push((line_start, line_end));
line_start = abs_pos + 1;
}
if line_start < chunk_end && chunk_end == data_len {
let mut line_end = chunk_end;
if is_newline && line_end > line_start && unsafe { *dp.add(line_end - 1) } == b'\r'
{
line_end -= 1;
found_cr = true;
}
offsets.push((line_start, line_end));
}
(offsets, found_cr)
})
.collect();
let total: usize = chunk_results.iter().map(|(v, _)| v.len()).sum();
let has_cr = chunk_results.iter().any(|(_, cr)| *cr);
let mut offsets = Vec::with_capacity(total);
for (chunk, _) in chunk_results {
offsets.extend_from_slice(&chunk);
}
(offsets, has_cr)
}
#[allow(clippy::type_complexity)]
fn read_all_input(
inputs: &[String],
zero_terminated: bool,
) -> io::Result<(FileData, Vec<(usize, usize)>, bool)> {
let delimiter = if zero_terminated { b'\0' } else { b'\n' };
let buffer = if inputs.len() == 1 && inputs[0] != "-" {
let file = File::open(&inputs[0]).map_err(|e| {
io::Error::new(
e.kind(),
format!("open failed: {}: {}", &inputs[0], io_error_msg(&e)),
)
})?;
let metadata = file.metadata()?;
if metadata.len() > 0 {
let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
#[cfg(target_os = "linux")]
{
if metadata.len() >= 2 * 1024 * 1024 {
let _ = mmap.advise(memmap2::Advice::HugePage);
}
let _ = mmap.advise(memmap2::Advice::Sequential);
if metadata.len() >= 4 * 1024 * 1024 {
if mmap.advise(memmap2::Advice::PopulateRead).is_err() {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
} else {
let _ = mmap.advise(memmap2::Advice::WillNeed);
}
}
FileData::Mmap(mmap)
} else {
FileData::Owned(Vec::new())
}
} else if inputs.len() == 1 && inputs[0] == "-" {
let stdin_data = crate::common::io::read_stdin()?;
FileData::Owned(stdin_data)
} else {
let mut data = Vec::new();
for input in inputs {
if input == "-" {
let stdin_data = crate::common::io::read_stdin()?;
if data.is_empty() {
data = stdin_data;
} else {
data.extend_from_slice(&stdin_data);
}
} else {
let mut file = File::open(input).map_err(|e| {
io::Error::new(
e.kind(),
format!("open failed: {}: {}", input, io_error_msg(&e)),
)
})?;
file.read_to_end(&mut data)?;
}
}
FileData::Owned(data)
};
let data = &*buffer;
let (offsets, has_cr) = if data.len() > 2 * 1024 * 1024 {
find_lines_parallel(data, delimiter)
} else {
let dp = data.as_ptr();
let mut offsets = Vec::with_capacity(data.len() / 40 + 1);
let mut start = 0usize;
let mut found_cr = false;
for pos in memchr::memchr_iter(delimiter, data) {
let mut end = pos;
if delimiter == b'\n' && end > start && unsafe { *dp.add(end - 1) } == b'\r' {
end -= 1;
found_cr = true;
}
offsets.push((start, end));
start = pos + 1;
}
if start < data.len() {
let mut end = data.len();
if delimiter == b'\n' && end > start && unsafe { *dp.add(end - 1) } == b'\r' {
end -= 1;
found_cr = true;
}
offsets.push((start, end));
}
(offsets, found_cr)
};
Ok((buffer, offsets, has_cr))
}
pub fn read_lines(inputs: &[String], zero_terminated: bool) -> io::Result<Vec<Vec<u8>>> {
let delimiter = if zero_terminated { b'\0' } else { b'\n' };
let mut lines = Vec::new();
for input in inputs {
if input == "-" {
let stdin = io::stdin();
let reader = BufReader::new(stdin.lock());
read_delimited_lines(reader, delimiter, &mut lines)?;
} else {
let file = File::open(input).map_err(|e| {
io::Error::new(
e.kind(),
format!("open failed: {}: {}", input, io_error_msg(&e)),
)
})?;
let reader = BufReader::with_capacity(256 * 1024, file);
read_delimited_lines(reader, delimiter, &mut lines)?;
}
}
Ok(lines)
}
fn read_delimited_lines<R: Read>(
mut reader: BufReader<R>,
delimiter: u8,
lines: &mut Vec<Vec<u8>>,
) -> io::Result<()> {
let mut buf = Vec::with_capacity(256);
loop {
buf.clear();
let n = reader.read_until(delimiter, &mut buf)?;
if n == 0 {
break;
}
if buf.last() == Some(&delimiter) {
buf.pop();
}
if delimiter == b'\n' && buf.last() == Some(&b'\r') {
buf.pop();
}
lines.push(buf.clone());
}
Ok(())
}
pub fn check_sorted(inputs: &[String], config: &SortConfig) -> io::Result<bool> {
let (buffer, offsets, _has_cr) = read_all_input(inputs, config.zero_terminated)?;
let data = &*buffer;
let dp = data.as_ptr();
for i in 1..offsets.len() {
let (s1, e1) = offsets[i - 1];
let (s2, e2) = offsets[i];
let line1 = unsafe { std::slice::from_raw_parts(dp.add(s1), e1 - s1) };
let line2 = unsafe { std::slice::from_raw_parts(dp.add(s2), e2 - s2) };
let cmp = if config.unique {
compare_lines_for_dedup(line1, line2, config)
} else {
compare_lines(line1, line2, config)
};
let bad = if config.unique {
cmp != Ordering::Less
} else {
cmp == Ordering::Greater
};
if bad {
if config.check == CheckMode::Diagnose {
let line_display = String::from_utf8_lossy(&data[s2..e2]);
let filename = if inputs.is_empty() || inputs[0] == "-" {
"-"
} else {
&inputs[0]
};
eprintln!("sort: {}:{}: disorder: {}", filename, i + 1, line_display);
}
return Ok(false);
}
}
Ok(true)
}
struct MergeEntry {
line: Vec<u8>,
file_idx: usize,
seq: u64,
}
pub fn merge_sorted(
inputs: &[String],
config: &SortConfig,
writer: &mut impl Write,
) -> io::Result<()> {
let delimiter = if config.zero_terminated { b'\0' } else { b'\n' };
let terminator: &[u8] = if config.zero_terminated { b"\0" } else { b"\n" };
let mut readers: Vec<Box<dyn BufRead>> = Vec::with_capacity(inputs.len());
for input in inputs {
if input == "-" {
readers.push(Box::new(BufReader::with_capacity(
256 * 1024,
io::stdin().lock(),
)));
} else {
let file = File::open(input)?;
readers.push(Box::new(BufReader::with_capacity(256 * 1024, file)));
}
}
let read_next = |reader: &mut dyn BufRead, delim: u8| -> io::Result<Option<Vec<u8>>> {
let mut buf = Vec::with_capacity(256);
let n = reader.read_until(delim, &mut buf)?;
if n == 0 {
return Ok(None);
}
if buf.last() == Some(&delim) {
buf.pop();
}
if delim == b'\n' && buf.last() == Some(&b'\r') {
buf.pop();
}
Ok(Some(buf))
};
let mut seq: u64 = 0;
let mut heap: BinaryHeap<std::cmp::Reverse<MergeEntryOrd>> =
BinaryHeap::with_capacity(inputs.len());
let config_arc = Arc::new(config.clone());
for (i, reader) in readers.iter_mut().enumerate() {
if let Some(line) = read_next(reader.as_mut(), delimiter)? {
heap.push(std::cmp::Reverse(MergeEntryOrd {
entry: MergeEntry {
line,
file_idx: i,
seq,
},
config: Arc::clone(&config_arc),
}));
seq += 1;
}
}
let mut prev_line: Option<Vec<u8>> = None;
const MERGE_BATCH: usize = 256 * 1024;
let mut batch_buf: Vec<u8> = Vec::with_capacity(MERGE_BATCH);
while let Some(std::cmp::Reverse(min)) = heap.pop() {
let should_output = if config.unique {
match &prev_line {
Some(prev) => {
compare_lines_for_dedup(prev, &min.entry.line, config) != Ordering::Equal
}
None => true,
}
} else {
true
};
if should_output {
batch_buf.extend_from_slice(&min.entry.line);
batch_buf.extend_from_slice(terminator);
if batch_buf.len() >= MERGE_BATCH {
writer.write_all(&batch_buf)?;
batch_buf.clear();
}
if config.unique {
prev_line = Some(min.entry.line.clone());
}
}
let file_idx = min.entry.file_idx;
let entry_config = Arc::clone(&min.config);
if let Some(next_line) = read_next(readers[file_idx].as_mut(), delimiter)? {
heap.push(std::cmp::Reverse(MergeEntryOrd {
entry: MergeEntry {
line: next_line,
file_idx,
seq,
},
config: entry_config,
}));
seq += 1;
}
}
if !batch_buf.is_empty() {
writer.write_all(&batch_buf)?;
}
Ok(())
}
struct MergeEntryOrd {
entry: MergeEntry,
config: Arc<SortConfig>,
}
impl PartialEq for MergeEntryOrd {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for MergeEntryOrd {}
impl PartialOrd for MergeEntryOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MergeEntryOrd {
fn cmp(&self, other: &Self) -> Ordering {
let ord = compare_lines(&self.entry.line, &other.entry.line, &self.config);
match ord {
Ordering::Equal => self.entry.seq.cmp(&other.entry.seq),
_ => ord,
}
}
}
#[inline]
fn write_all_vectored_sort(writer: &mut SortOutput, slices: &[io::IoSlice<'_>]) -> io::Result<()> {
let total: usize = slices.iter().map(|s| s.len()).sum();
let written = writer.write_vectored_direct(slices)?;
if written >= total {
return Ok(());
}
if written == 0 {
return Err(io::Error::new(io::ErrorKind::WriteZero, "write zero"));
}
let mut skip = written;
for slice in slices {
let len = slice.len();
if skip >= len {
skip -= len;
continue;
}
writer.write_all_direct(&slice[skip..])?;
skip = 0;
}
Ok(())
}
pub(crate) fn is_c_locale() -> bool {
use std::sync::OnceLock;
static IS_C: OnceLock<bool> = OnceLock::new();
*IS_C.get_or_init(|| unsafe {
let lc = libc::setlocale(libc::LC_COLLATE, std::ptr::null());
if lc.is_null() {
return true;
}
let name = std::ffi::CStr::from_ptr(lc).to_string_lossy();
name == "C" || name == "POSIX"
})
}
fn compute_xfrm_key(key: &[u8]) -> Option<Vec<u8>> {
if key.is_empty() {
return Some(Vec::new());
}
if memchr::memchr(0, key).is_some() {
return None;
}
let mut c_buf = vec![0u8; key.len() + 1];
c_buf[..key.len()].copy_from_slice(key);
let needed = unsafe { libc::strxfrm(std::ptr::null_mut(), c_buf.as_ptr() as *const _, 0) };
if needed == usize::MAX {
return None;
}
let mut out = vec![0u8; needed + 1];
unsafe {
libc::strxfrm(
out.as_mut_ptr() as *mut _,
c_buf.as_ptr() as *const _,
needed + 1,
);
}
out.truncate(needed);
Some(out)
}
fn compute_xfrm_key_reuse(key: &[u8], c_buf: &mut Vec<u8>) -> Option<Vec<u8>> {
if key.is_empty() {
return Some(Vec::new());
}
if memchr::memchr(0, key).is_some() {
return None;
}
if key.len() + 1 > c_buf.len() {
c_buf.resize(key.len() + 1, 0);
}
c_buf[..key.len()].copy_from_slice(key);
c_buf[key.len()] = 0;
let needed = unsafe { libc::strxfrm(std::ptr::null_mut(), c_buf.as_ptr() as *const _, 0) };
if needed == usize::MAX {
return None;
}
let mut out = vec![0u8; needed + 1];
unsafe {
libc::strxfrm(
out.as_mut_ptr() as *mut _,
c_buf.as_ptr() as *const _,
needed + 1,
);
}
out.truncate(needed);
Some(out)
}
#[inline(always)]
fn prefetch_read(ptr: *const u8) {
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
}
#[cfg(target_arch = "aarch64")]
unsafe {
std::arch::asm!("prfm pldl1keep, [{x}]", x = in(reg) ptr, options(nostack, preserves_flags));
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = ptr;
}
#[inline(always)]
fn prefetch_write(ptr: *const u8) {
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_ET0);
}
#[cfg(target_arch = "aarch64")]
unsafe {
std::arch::asm!("prfm pstl1keep, [{x}]", x = in(reg) ptr, options(nostack, preserves_flags));
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
let _ = ptr;
}
#[inline]
fn line_prefix(data: &[u8], start: usize, end: usize) -> u64 {
let len = end - start;
if len >= 8 {
u64::from_be_bytes(unsafe { *(data.as_ptr().add(start) as *const [u8; 8]) })
} else {
let mut bytes = [0u8; 8];
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr().add(start), bytes.as_mut_ptr(), len);
}
u64::from_be_bytes(bytes)
}
}
#[inline]
fn line_prefix_upper(data: &[u8], start: usize, end: usize) -> u64 {
let len = end - start;
let mut bytes = [0u8; 8];
let take = len.min(8);
let p = data.as_ptr();
let mut i = 0usize;
while i < take {
let b = unsafe { *p.add(start + i) };
bytes[i] = if b >= b'a' && b <= b'z' { b - 32 } else { b };
i += 1;
}
u64::from_be_bytes(bytes)
}
fn pre_extract_key_offsets(
data: &[u8],
offsets: &[(usize, usize)],
key: &KeyDef,
separator: Option<u8>,
ignore_leading_blanks: bool,
zero_terminated: bool,
) -> Vec<(usize, usize)> {
let is_whole_field = separator.is_some()
&& key.start_char == 0
&& key.end_char == 0
&& (key.end_field == 0 || key.end_field == key.start_field);
if is_whole_field {
let sep = separator.unwrap();
let field_idx = key.start_field.saturating_sub(1);
let to_end = key.end_field == 0; let extract_fast = move |&(s, e): &(usize, usize)| {
let line = &data[s..e];
let mut fstart = 0usize;
for _ in 0..field_idx {
match memchr::memchr(sep, &line[fstart..]) {
Some(pos) => fstart = fstart + pos + 1,
None => return (0usize, 0usize),
}
}
if fstart > line.len() {
return (0, 0);
}
if to_end {
(s + fstart, e)
} else {
match memchr::memchr(sep, &line[fstart..]) {
Some(pos) => (s + fstart, s + fstart + pos),
None => (s + fstart, e),
}
}
};
return if offsets.len() > 10_000 {
offsets.par_iter().map(extract_fast).collect()
} else {
offsets.iter().map(extract_fast).collect()
};
}
let extract = |&(s, e): &(usize, usize)| {
let line = &data[s..e];
let extracted = extract_key_z(line, key, separator, ignore_leading_blanks, zero_terminated);
if extracted.is_empty() {
(0, 0)
} else {
let offset_in_data = unsafe { extracted.as_ptr().offset_from(data.as_ptr()) as usize };
(offset_in_data, offset_in_data + extracted.len())
}
};
if offsets.len() > 10_000 {
offsets.par_iter().map(extract).collect()
} else {
offsets.iter().map(extract).collect()
}
}
fn parse_to_sortable_u64(slice: &[u8], opts: &KeyOpts) -> u64 {
if opts.human_numeric {
human_numeric_to_sortable_u64(slice)
} else if opts.general_numeric {
float_to_sortable_u64(parse_general_numeric(slice))
} else {
float_to_sortable_u64(parse_numeric_value(slice))
}
}
#[inline]
fn float_to_sortable_u64(f: f64) -> u64 {
if f.is_nan() {
return 0; }
let bits = f.to_bits();
if (bits >> 63) == 0 {
bits ^ 0x8000000000000000 } else {
!bits }
}
fn is_debug_no_match(key: &[u8], key_opts: &KeyOpts, global_opts: &KeyOpts) -> bool {
if key.is_empty() {
return true;
}
let is_numeric = key_opts.numeric || global_opts.numeric;
let is_general_numeric = key_opts.general_numeric || global_opts.general_numeric;
let is_human_numeric = key_opts.human_numeric || global_opts.human_numeric;
let is_month = key_opts.month || global_opts.month;
if is_numeric || is_human_numeric {
let trimmed = key.iter().position(|&b| b != b' ' && b != b'\t');
match trimmed {
None => true,
Some(pos) => {
let b = key[pos];
!(b.is_ascii_digit() || b == b'+' || b == b'-' || b == b'.')
}
}
} else if is_general_numeric {
let trimmed = key.iter().position(|&b| b != b' ' && b != b'\t');
match trimmed {
None => true,
Some(pos) => {
let b = key[pos];
!(b.is_ascii_digit()
|| b == b'+'
|| b == b'-'
|| b == b'.'
|| b == b'n'
|| b == b'N'
|| b == b'i'
|| b == b'I')
}
}
} else if is_month {
let trimmed: Vec<u8> = key
.iter()
.skip_while(|&&b| b == b' ' || b == b'\t')
.take(3)
.map(|&b| b.to_ascii_uppercase())
.collect();
!matches!(
trimmed.as_slice(),
b"JAN"
| b"FEB"
| b"MAR"
| b"APR"
| b"MAY"
| b"JUN"
| b"JUL"
| b"AUG"
| b"SEP"
| b"OCT"
| b"NOV"
| b"DEC"
)
} else {
false
}
}
fn write_debug_output(
data: &[u8],
offsets: &[(usize, usize)],
sorted_indices: &[usize],
config: &SortConfig,
writer: &mut SortOutput,
terminator: &[u8],
) -> io::Result<()> {
let dp = data.as_ptr();
let term_byte = terminator[0];
let mut prev: Option<usize> = None;
for &idx in sorted_indices {
let (s, e) = offsets[idx];
let line = unsafe { std::slice::from_raw_parts(dp.add(s), e - s) };
if config.unique {
let should_output = match prev {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line = unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
compare_lines_for_dedup(prev_line, line, config) != Ordering::Equal
}
None => true,
};
if !should_output {
continue;
}
prev = Some(idx);
}
writer.write_all(line)?;
writer.write_all(&[term_byte])?;
if !config.keys.is_empty() {
for key_def in &config.keys {
let key = extract_key_z(
line,
key_def,
config.separator,
key_def.opts.ignore_leading_blanks || config.global_opts.ignore_leading_blanks,
config.zero_terminated,
);
let key_start = if !key.is_empty() {
let line_ptr = line.as_ptr() as usize;
let key_ptr = key.as_ptr() as usize;
if key_ptr >= line_ptr && key_ptr < line_ptr + line.len() {
key_ptr - line_ptr
} else {
0
}
} else {
line.len()
};
if is_debug_no_match(key, &key_def.opts, &config.global_opts) {
let mut annotation = vec![b' '; key_start];
annotation.extend_from_slice(b"^ no match for key");
writer.write_all(&annotation)?;
writer.write_all(&[term_byte])?;
} else {
let key_len = key.len().max(1);
let mut annotation = vec![b' '; key_start];
annotation.extend(std::iter::repeat_n(b'_', key_len));
writer.write_all(&annotation)?;
writer.write_all(&[term_byte])?;
}
}
}
if !config.stable {
let line_len = line.len().max(1);
let annotation: Vec<u8> = std::iter::repeat_n(b'_', line_len).collect();
writer.write_all(&annotation)?;
writer.write_all(&[term_byte])?;
}
}
writer.flush()?;
Ok(())
}
fn write_sorted_output(
data: &[u8],
offsets: &[(usize, usize)],
sorted_indices: &[usize],
config: &SortConfig,
writer: &mut SortOutput,
terminator: &[u8],
) -> io::Result<()> {
let dp = data.as_ptr();
let n = sorted_indices.len();
let term_byte = terminator[0];
if config.unique {
const BATCH_U: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH_U);
let mut prev: Option<usize> = None;
for &idx in sorted_indices {
let (s, e) = offsets[idx];
let len = e - s;
let should_output = match prev {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line = unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
let line = unsafe { std::slice::from_raw_parts(dp.add(s), len) };
compare_lines_for_dedup(prev_line, line, config) != Ordering::Equal
}
None => true,
};
if should_output {
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH_U {
write_all_vectored_sort(writer, &slices)?;
slices.clear();
}
prev = Some(idx);
}
}
if !slices.is_empty() {
write_all_vectored_sort(writer, &slices)?;
}
} else {
const BATCH: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
for j in 0..n {
let (s, e) = offsets[sorted_indices[j]];
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(writer, &slices)?;
slices.clear();
}
}
if !slices.is_empty() {
write_all_vectored_sort(writer, &slices)?;
}
}
Ok(())
}
fn write_sorted_entries(
data: &[u8],
offsets: &[(usize, usize)],
entries: &[(u64, usize)],
config: &SortConfig,
writer: &mut SortOutput,
terminator: &[u8],
) -> io::Result<()> {
let dp = data.as_ptr();
let n = entries.len();
let term_byte = terminator[0];
if config.unique {
const BATCH_U: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH_U);
let mut prev: Option<usize> = None;
for &(_, idx) in entries {
let (s, e) = offsets[idx];
let len = e - s;
let should_output = match prev {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line = unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
let line = unsafe { std::slice::from_raw_parts(dp.add(s), len) };
compare_lines_for_dedup(prev_line, line, config) != Ordering::Equal
}
None => true,
};
if should_output {
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH_U {
write_all_vectored_sort(writer, &slices)?;
slices.clear();
}
prev = Some(idx);
}
}
if !slices.is_empty() {
write_all_vectored_sort(writer, &slices)?;
}
} else {
const BATCH: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
for j in 0..n {
let (s, e) = offsets[entries[j].1];
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(writer, &slices)?;
slices.clear();
}
}
if !slices.is_empty() {
write_all_vectored_sort(writer, &slices)?;
}
}
Ok(())
}
fn radix_sort_numeric_entries(
entries: Vec<(u64, usize)>,
data: &[u8],
offsets: &[(usize, usize)],
stable: bool,
_reverse: bool,
) -> Vec<(u64, usize)> {
let n = entries.len();
if n <= 1 {
return entries;
}
let nbk: usize = 65536;
let first_val = entries[0].0;
let mut xor_all = 0u64;
for &(val, _) in &entries[1..] {
xor_all |= val ^ first_val;
}
let mut passes_needed: Vec<u32> = Vec::with_capacity(4);
for pass in 0..4u32 {
let shift = pass * 16;
if ((xor_all >> shift) & 0xFFFF) != 0 {
passes_needed.push(pass);
}
}
if passes_needed.is_empty() {
let mut sorted = entries;
if !stable {
let dp = data.as_ptr();
let mut i = 0;
while i < n {
let key = sorted[i].0;
let mut j = i + 1;
while j < n && sorted[j].0 == key {
j += 1;
}
if j - i > 1 {
sorted[i..j].sort_unstable_by(|a, b| unsafe {
let (sa, ea) = offsets[a.1];
let (sb, eb) = offsets[b.1];
std::slice::from_raw_parts(dp.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp.add(sb), eb - sb))
});
}
i = j;
}
}
return sorted;
}
let mut src = entries;
let mut dst: Vec<(u64, usize)> = Vec::with_capacity(n);
#[allow(clippy::uninit_vec)]
unsafe {
dst.set_len(n);
}
let mut cnts = vec![0u32; nbk];
for &pass in &passes_needed {
let shift = pass * 16;
cnts.iter_mut().for_each(|c| *c = 0);
for &(val, _) in &src {
cnts[((val >> shift) & 0xFFFF) as usize] += 1;
}
let mut sum = 0u32;
for c in cnts.iter_mut() {
let old = *c;
*c = sum;
sum += old;
}
let dst_ptr = dst.as_mut_ptr();
let src_ptr = src.as_ptr();
let pfx_dist = 8usize;
for idx in 0..n {
let ent = unsafe { *src_ptr.add(idx) };
let b = ((ent.0 >> shift) & 0xFFFF) as usize;
unsafe {
*dst_ptr.add(cnts[b] as usize) = ent;
}
cnts[b] += 1;
if idx + pfx_dist < n {
prefetch_read(unsafe { src_ptr.add(idx + pfx_dist) as *const u8 });
let future = unsafe { *src_ptr.add(idx + pfx_dist) };
let fb = ((future.0 >> shift) & 0xFFFF) as usize;
prefetch_write(unsafe { dst_ptr.add(cnts[fb] as usize) as *const u8 });
}
}
std::mem::swap(&mut src, &mut dst);
}
let mut sorted = src;
if !stable {
let dp = data.as_ptr();
let mut i = 0;
while i < n {
let key = sorted[i].0;
let mut j = i + 1;
while j < n && sorted[j].0 == key {
j += 1;
}
if j - i > 1 {
sorted[i..j].sort_unstable_by(|a, b| unsafe {
let (sa, ea) = offsets[a.1];
let (sb, eb) = offsets[b.1];
std::slice::from_raw_parts(dp.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp.add(sb), eb - sb))
});
}
i = j;
}
}
sorted
}
fn radix_sort_lex_entries(
entries: Vec<(u64, u32, u32)>,
data: &[u8],
stable: bool,
) -> Vec<(u64, u32, u32)> {
let n = entries.len();
if n <= 1 {
return entries;
}
let nbk: usize = 65536;
let first_val = entries[0].0;
let mut xor_all = 0u64;
for &(val, _, _) in &entries[1..] {
xor_all |= val ^ first_val;
}
let mut passes_needed: Vec<u32> = Vec::with_capacity(4);
for pass in 0..4u32 {
let shift = pass * 16;
if ((xor_all >> shift) & 0xFFFF) != 0 {
passes_needed.push(pass);
}
}
if passes_needed.is_empty() {
let mut sorted = entries;
sort_equal_lex_groups(&mut sorted, data, stable);
return sorted;
}
let mut src = entries;
let mut dst: Vec<(u64, u32, u32)> = Vec::with_capacity(n);
#[allow(clippy::uninit_vec)]
unsafe {
dst.set_len(n);
}
let mut cnts = vec![0u32; nbk];
for &pass in &passes_needed {
let shift = pass * 16;
cnts.iter_mut().for_each(|c| *c = 0);
for &(val, _, _) in &src {
cnts[((val >> shift) & 0xFFFF) as usize] += 1;
}
let mut sum = 0u32;
for c in cnts.iter_mut() {
let old = *c;
*c = sum;
sum += old;
}
let dst_ptr = dst.as_mut_ptr();
let src_ptr = src.as_ptr();
let pfx_dist = 8usize;
for idx in 0..n {
let ent = unsafe { *src_ptr.add(idx) };
let b = ((ent.0 >> shift) & 0xFFFF) as usize;
unsafe {
*dst_ptr.add(cnts[b] as usize) = ent;
}
cnts[b] += 1;
if idx + pfx_dist < n {
prefetch_read(unsafe { src_ptr.add(idx + pfx_dist) as *const u8 });
let future = unsafe { *src_ptr.add(idx + pfx_dist) };
let fb = ((future.0 >> shift) & 0xFFFF) as usize;
prefetch_write(unsafe { dst_ptr.add(cnts[fb] as usize) as *const u8 });
}
}
std::mem::swap(&mut src, &mut dst);
}
let mut sorted = src;
sort_equal_lex_groups(&mut sorted, data, stable);
sorted
}
fn sort_equal_lex_groups(sorted: &mut [(u64, u32, u32)], data: &[u8], stable: bool) {
let n = sorted.len();
let dp = data.as_ptr() as usize;
let mut i = 0;
while i < n {
let key = sorted[i].0;
let mut j = i + 1;
while j < n && sorted[j].0 == key {
j += 1;
}
if j - i > 1 {
let cmp = |a: &(u64, u32, u32), b: &(u64, u32, u32)| -> Ordering {
let la = a.2 as usize;
let lb = b.2 as usize;
let skip_a = 8.min(la);
let skip_b = 8.min(lb);
let rem_a = la - skip_a;
let rem_b = lb - skip_b;
unsafe {
let p = dp as *const u8;
let pa = p.add(a.1 as usize + skip_a);
let pb = p.add(b.1 as usize + skip_b);
let min_rem = rem_a.min(rem_b);
let mut k = 0usize;
while k + 8 <= min_rem {
let wa = u64::from_be_bytes(*(pa.add(k) as *const [u8; 8]));
let wb = u64::from_be_bytes(*(pb.add(k) as *const [u8; 8]));
if wa != wb {
return wa.cmp(&wb);
}
k += 8;
}
std::slice::from_raw_parts(pa.add(k), rem_a - k)
.cmp(std::slice::from_raw_parts(pb.add(k), rem_b - k))
}
};
if stable {
sorted[i..j].sort_by(cmp);
} else {
sorted[i..j].sort_unstable_by(cmp);
}
}
i = j;
}
}
const PARALLEL_SORT_THRESHOLD: usize = 10_000;
fn do_sort(
indices: &mut [usize],
stable: bool,
cmp: impl Fn(&usize, &usize) -> Ordering + Send + Sync,
) {
let n = indices.len();
if stable {
if n > PARALLEL_SORT_THRESHOLD {
indices.par_sort_by(cmp);
} else {
indices.sort_by(cmp);
}
} else if n > PARALLEL_SORT_THRESHOLD {
indices.par_sort_unstable_by(cmp);
} else {
indices.sort_unstable_by(cmp);
}
}
pub fn sort_and_output(inputs: &[String], config: &SortConfig) -> io::Result<()> {
#[cfg(target_os = "linux")]
{
const PIPE_SIZE: i32 = 8 * 1024 * 1024;
unsafe {
libc::fcntl(0, libc::F_SETPIPE_SZ, PIPE_SIZE);
libc::fcntl(1, libc::F_SETPIPE_SZ, PIPE_SIZE);
}
}
let parallel_count = config.parallel;
std::thread::spawn(move || match parallel_count {
Some(n) => {
rayon::ThreadPoolBuilder::new()
.num_threads(n.max(1))
.build_global()
.ok();
}
None => {
rayon::ThreadPoolBuilder::new().build_global().ok();
}
});
if config.check != CheckMode::None {
let sorted = check_sorted(inputs, config)?;
if !sorted {
std::process::exit(1);
}
return Ok(());
}
if config.merge {
let mut writer = if let Some(ref path) = config.output_file {
SortOutput::File(BufWriter::with_capacity(
OUTPUT_BUF_SIZE,
File::create(path)?,
))
} else {
SortOutput::stdout()
};
return merge_sorted(inputs, config, &mut writer);
}
let (buffer, offsets, has_cr) = read_all_input(inputs, config.zero_terminated)?;
let data: &[u8] = &buffer;
let num_lines = offsets.len();
let mut writer = if let Some(ref path) = config.output_file {
SortOutput::File(BufWriter::with_capacity(
OUTPUT_BUF_SIZE,
File::create(path)?,
))
} else {
SortOutput::stdout()
};
if config.debug {
let terminator: &[u8] = if config.zero_terminated { b"\0" } else { b"\n" };
if num_lines == 0 {
return Ok(());
}
let mut indices: Vec<usize> = (0..num_lines).collect();
if config.stable {
indices.sort_by(|&a, &b| {
compare_lines(
&data[offsets[a].0..offsets[a].1],
&data[offsets[b].0..offsets[b].1],
config,
)
});
} else {
indices.sort_unstable_by(|&a, &b| {
compare_lines(
&data[offsets[a].0..offsets[a].1],
&data[offsets[b].0..offsets[b].1],
config,
)
});
}
return write_debug_output(data, &offsets, &indices, config, &mut writer, terminator);
}
if num_lines == 0 {
return Ok(());
}
let terminator: &[u8] = if config.zero_terminated { b"\0" } else { b"\n" };
let no_keys = config.keys.is_empty();
let gopts = &config.global_opts;
let is_plain_lex_for_check = no_keys
&& !gopts.has_sort_type()
&& !gopts.dictionary_order
&& !gopts.ignore_case
&& !gopts.ignore_nonprinting
&& !gopts.ignore_leading_blanks;
let is_numeric_only_precheck = no_keys
&& (gopts.numeric || gopts.general_numeric || gopts.human_numeric)
&& !gopts.dictionary_order
&& !gopts.ignore_case
&& !gopts.ignore_nonprinting;
let skip_generic_sorted_check = is_plain_lex_for_check && num_lines > 256;
if num_lines > 1 && !is_numeric_only_precheck && !skip_generic_sorted_check {
let (is_sorted, is_reverse_sorted) = if is_plain_lex_for_check {
let dp = data.as_ptr();
let reverse = gopts.reverse;
let mut asc = true;
let mut desc = true;
let mut prev_prefix = line_prefix(data, offsets[0].0, offsets[0].1);
for i in 1..num_lines {
let (s2, e2) = offsets[i];
let cur_prefix = line_prefix(data, s2, e2);
if asc {
if prev_prefix > cur_prefix {
asc = false;
} else if prev_prefix == cur_prefix {
let (s1, e1) = offsets[i - 1];
let a = unsafe { std::slice::from_raw_parts(dp.add(s1), e1 - s1) };
let b = unsafe { std::slice::from_raw_parts(dp.add(s2), e2 - s2) };
if a > b {
asc = false;
}
}
}
if desc {
if prev_prefix < cur_prefix {
desc = false;
} else if prev_prefix == cur_prefix {
let (s1, e1) = offsets[i - 1];
let a = unsafe { std::slice::from_raw_parts(dp.add(s1), e1 - s1) };
let b = unsafe { std::slice::from_raw_parts(dp.add(s2), e2 - s2) };
if a < b {
desc = false;
}
}
}
if !asc && !desc {
break;
}
prev_prefix = cur_prefix;
}
if reverse { (desc, asc) } else { (asc, desc) }
} else {
let mut asc = true;
let mut desc = true;
for i in 1..num_lines {
let (s1, e1) = offsets[i - 1];
let (s2, e2) = offsets[i];
let cmp = compare_lines(&data[s1..e1], &data[s2..e2], config);
match cmp {
Ordering::Greater => asc = false,
Ordering::Less => desc = false,
_ => {}
}
if !asc && !desc {
break;
}
}
(asc, desc)
};
if is_sorted {
if !config.unique && !config.zero_terminated && !has_cr {
if data.last() == Some(&b'\n') {
writer.write_all_direct(data)?;
} else if !data.is_empty() {
writer.write_all(data)?;
writer.write_all(b"\n")?;
}
writer.flush()?;
return Ok(());
}
let dp = data.as_ptr();
let term_byte = terminator[0];
let term_sl: &[u8] = &[term_byte];
let data_len = data.len();
const BATCH: usize = 1024;
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
if config.unique {
let mut prev: Option<usize> = None;
for i in 0..num_lines {
let (s, e) = offsets[i];
let len = e - s;
let line = unsafe { std::slice::from_raw_parts(dp.add(s), len) };
let emit = match prev {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line =
unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
compare_lines_for_dedup(prev_line, line, config) != Ordering::Equal
}
None => true,
};
if emit {
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
prev = Some(i);
}
}
} else {
for i in 0..num_lines {
let (s, e) = offsets[i];
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
writer.flush()?;
return Ok(());
}
if is_reverse_sorted {
let dp = data.as_ptr();
let term_byte = terminator[0];
let term_sl: &[u8] = &[term_byte];
let data_len = data.len();
const BATCH: usize = 1024;
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
let mut prev: Option<usize> = None;
for i in (0..num_lines).rev() {
let (s, e) = offsets[i];
if config.unique {
let len = e - s;
let line = unsafe { std::slice::from_raw_parts(dp.add(s), len) };
let emit = match prev {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line =
unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
compare_lines_for_dedup(prev_line, line, config) != Ordering::Equal
}
None => true,
};
if !emit {
continue;
}
prev = Some(i);
}
if e < data_len && data[e] == term_byte {
slices.push(io::IoSlice::new(&data[s..e + 1]));
} else {
slices.push(io::IoSlice::new(&data[s..e]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
writer.flush()?;
return Ok(());
}
}
#[cfg(target_os = "linux")]
if data.len() > 4 * 1024 * 1024 {
if let FileData::Mmap(ref mmap) = buffer {
let _ = mmap.advise(memmap2::Advice::Random);
}
}
let is_plain_lex = no_keys
&& !gopts.has_sort_type()
&& !gopts.dictionary_order
&& !gopts.ignore_case
&& !gopts.ignore_nonprinting
&& !gopts.ignore_leading_blanks;
let is_fold_case_lex = no_keys
&& !gopts.has_sort_type()
&& !gopts.dictionary_order
&& gopts.ignore_case
&& !gopts.ignore_nonprinting;
let is_numeric_only = no_keys
&& (gopts.numeric || gopts.general_numeric || gopts.human_numeric)
&& !gopts.dictionary_order
&& !gopts.ignore_case
&& !gopts.ignore_nonprinting;
let is_single_key = config.keys.len() == 1;
if is_plain_lex && num_lines > 256 {
let reverse = gopts.reverse;
let entries: Vec<(u64, u32, u32)> = if num_lines > 10_000 {
offsets
.par_iter()
.map(|&(s, e)| (line_prefix(data, s, e), s as u32, (e - s) as u32))
.collect()
} else {
offsets
.iter()
.map(|&(s, e)| (line_prefix(data, s, e), s as u32, (e - s) as u32))
.collect()
};
if num_lines > 1 && !config.zero_terminated {
let dp = data.as_ptr();
let mut is_sorted_fwd = true;
let mut is_sorted_rev = true;
let stable = config.stable;
for i in 1..entries.len() {
let (pa, sa, la) = entries[i - 1];
let (pb, sb, lb) = entries[i];
let ord = match pa.cmp(&pb) {
Ordering::Equal => unsafe {
let skip_a = 8.min(la as usize);
let skip_b = 8.min(lb as usize);
std::slice::from_raw_parts(
dp.add(sa as usize + skip_a),
la as usize - skip_a,
)
.cmp(std::slice::from_raw_parts(
dp.add(sb as usize + skip_b),
lb as usize - skip_b,
))
},
ord => ord,
};
match ord {
Ordering::Greater => is_sorted_fwd = false,
Ordering::Less => is_sorted_rev = false,
Ordering::Equal => {
if !stable {
let line_ord = unsafe {
std::slice::from_raw_parts(dp.add(sa as usize), la as usize).cmp(
std::slice::from_raw_parts(dp.add(sb as usize), lb as usize),
)
};
if line_ord == Ordering::Greater {
is_sorted_fwd = false;
}
if line_ord == Ordering::Less {
is_sorted_rev = false;
}
}
}
}
if !is_sorted_fwd && !is_sorted_rev {
break;
}
}
if (!reverse && is_sorted_fwd) || (reverse && is_sorted_rev) {
if config.unique {
let term_byte = terminator[0];
const BATCH: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
let mut prev_pfx = u64::MAX;
let mut prev_s = u32::MAX;
let mut prev_l = 0u32;
for &(pfx, s, l) in &entries {
let emit = prev_s == u32::MAX
|| pfx != prev_pfx
|| l != prev_l
|| unsafe {
std::slice::from_raw_parts(dp.add(s as usize), l as usize)
!= std::slice::from_raw_parts(
dp.add(prev_s as usize),
prev_l as usize,
)
};
if emit {
let su = s as usize;
let end = su + l as usize;
if end < data_len && data[end] == term_byte {
slices.push(io::IoSlice::new(&data[su..end + 1]));
} else {
slices.push(io::IoSlice::new(&data[su..end]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
prev_pfx = pfx;
prev_s = s;
prev_l = l;
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
} else if data.last() == Some(&b'\n') {
writer.write_all_direct(data)?;
} else if !data.is_empty() {
writer.write_all(data)?;
writer.write_all(b"\n")?;
}
writer.flush()?;
return Ok(());
}
if (!reverse && is_sorted_rev) || (reverse && is_sorted_fwd) {
let term_byte = terminator[0];
let unique = config.unique;
const BATCH: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
let mut prev_pfx = u64::MAX;
let mut prev_s = u32::MAX;
let mut prev_l = 0u32;
for i in (0..entries.len()).rev() {
let (pfx, s, l) = entries[i];
if unique {
let emit = prev_s == u32::MAX
|| pfx != prev_pfx
|| l != prev_l
|| unsafe {
std::slice::from_raw_parts(dp.add(s as usize), l as usize)
!= std::slice::from_raw_parts(
dp.add(prev_s as usize),
prev_l as usize,
)
};
if !emit {
continue;
}
prev_pfx = pfx;
prev_s = s;
prev_l = l;
}
let su = s as usize;
let end = su + l as usize;
if end < data_len && data[end] == term_byte {
slices.push(io::IoSlice::new(&data[su..end + 1]));
} else {
slices.push(io::IoSlice::new(&data[su..end]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
writer.flush()?;
return Ok(());
}
}
let data_addr = data.as_ptr() as usize;
let pfx_cmp = |a: &(u64, u32, u32), b: &(u64, u32, u32)| -> Ordering {
match a.0.cmp(&b.0) {
Ordering::Equal => {
let la = a.2 as usize;
let lb = b.2 as usize;
let skip_a = 8.min(la);
let skip_b = 8.min(lb);
let rem_a = la - skip_a;
let rem_b = lb - skip_b;
unsafe {
let dp = data_addr as *const u8;
let pa = dp.add(a.1 as usize + skip_a);
let pb = dp.add(b.1 as usize + skip_b);
let min_rem = rem_a.min(rem_b);
let mut i = 0usize;
while i + 8 <= min_rem {
let wa = u64::from_be_bytes(*(pa.add(i) as *const [u8; 8]));
let wb = u64::from_be_bytes(*(pb.add(i) as *const [u8; 8]));
if wa != wb {
return wa.cmp(&wb);
}
i += 8;
}
std::slice::from_raw_parts(pa.add(i), rem_a - i)
.cmp(std::slice::from_raw_parts(pb.add(i), rem_b - i))
}
}
ord => ord,
}
};
let mut sorted = entries;
if num_lines > 512 {
sorted = radix_sort_lex_entries(sorted, data, config.stable);
} else if config.stable {
sorted.sort_by(pfx_cmp);
} else {
sorted.sort_unstable_by(pfx_cmp);
}
#[cfg(target_os = "linux")]
if let FileData::Mmap(ref mmap) = buffer {
let _ = mmap.advise(memmap2::Advice::Random);
}
let dp = data.as_ptr();
let n_sorted = sorted.len();
let term_byte = terminator[0];
if config.unique {
let mut prev_prefix = u64::MAX;
let mut prev_start = u32::MAX;
let mut prev_len = 0u32;
const BATCH: usize = 1024;
let data_len = data.len();
let term_sl: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
for idx in 0..n_sorted {
let actual_idx = if reverse { n_sorted - 1 - idx } else { idx };
let (pfx, s, l) = sorted[actual_idx];
let su = s as usize;
let lu = l as usize;
let emit = prev_start == u32::MAX || pfx != prev_prefix || {
let ps = prev_start as usize;
let pl = prev_len as usize;
if pl != lu {
true
} else {
unsafe {
std::slice::from_raw_parts(dp.add(ps), pl)
!= std::slice::from_raw_parts(dp.add(su), lu)
}
}
};
if emit {
let end = su + lu;
if end < data_len && data[end] == term_byte {
slices.push(io::IoSlice::new(&data[su..end + 1]));
} else {
slices.push(io::IoSlice::new(&data[su..end]));
slices.push(io::IoSlice::new(term_sl));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
prev_prefix = pfx;
prev_start = s;
prev_len = l;
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
} else {
const BATCH: usize = 1024;
let data_len = data.len();
let term_slice: &[u8] = &[term_byte];
let mut slices: Vec<io::IoSlice<'_>> = Vec::with_capacity(BATCH);
let iter_reverse = reverse;
for j in 0..n_sorted {
let actual = if iter_reverse { n_sorted - 1 - j } else { j };
let (_, s, l) = sorted[actual];
let su = s as usize;
let lu = l as usize;
let end = su + lu;
if end < data_len && data[end] == term_byte {
slices.push(io::IoSlice::new(&data[su..end + 1]));
} else {
slices.push(io::IoSlice::new(&data[su..end]));
slices.push(io::IoSlice::new(term_slice));
}
if slices.len() >= BATCH {
write_all_vectored_sort(&mut writer, &slices)?;
slices.clear();
}
}
if !slices.is_empty() {
write_all_vectored_sort(&mut writer, &slices)?;
}
}
} else if is_fold_case_lex && num_lines > 256 {
let reverse = gopts.reverse;
let needs_blank = gopts.ignore_leading_blanks;
let mut entries: Vec<(u64, usize)> = if num_lines > 10_000 {
offsets
.par_iter()
.enumerate()
.map(|(i, &(s, e))| {
let (s, e) = if needs_blank {
let trimmed = super::compare::skip_leading_blanks(&data[s..e]);
let new_s = unsafe { trimmed.as_ptr().offset_from(data.as_ptr()) as usize };
(new_s, new_s + trimmed.len())
} else {
(s, e)
};
(line_prefix_upper(data, s, e), i)
})
.collect()
} else {
offsets
.iter()
.enumerate()
.map(|(i, &(s, e))| {
let (s, e) = if needs_blank {
let trimmed = super::compare::skip_leading_blanks(&data[s..e]);
let new_s = unsafe { trimmed.as_ptr().offset_from(data.as_ptr()) as usize };
(new_s, new_s + trimmed.len())
} else {
(s, e)
};
(line_prefix_upper(data, s, e), i)
})
.collect()
};
let n = entries.len();
let stable = config.stable;
let dp_fold = data.as_ptr();
let fold_cmp = |a: &(u64, usize), b: &(u64, usize)| -> Ordering {
let (sa, ea) = offsets[a.1];
let (sb, eb) = offsets[b.1];
let la = unsafe { std::slice::from_raw_parts(dp_fold.add(sa), ea - sa) };
let lb = unsafe { std::slice::from_raw_parts(dp_fold.add(sb), eb - sb) };
let la = if needs_blank {
super::compare::skip_leading_blanks(la)
} else {
la
};
let lb = if needs_blank {
super::compare::skip_leading_blanks(lb)
} else {
lb
};
let ord = match a.0.cmp(&b.0) {
Ordering::Equal => super::compare::compare_ignore_case(la, lb),
ord => ord,
};
if ord == Ordering::Equal && !stable {
unsafe {
std::slice::from_raw_parts(dp_fold.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp_fold.add(sb), eb - sb))
}
} else {
ord
}
};
if n > 256 {
let mut entries = radix_sort_numeric_entries(entries, data, &offsets, stable, false);
{
let mut i = 0;
while i < n {
let key = entries[i].0;
let mut j = i + 1;
while j < n && entries[j].0 == key {
j += 1;
}
if j - i > 1 {
entries[i..j].sort_unstable_by(fold_cmp);
}
i = j;
}
}
if reverse {
entries.reverse();
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
} else {
let fold_cmp_rev = |a: &(u64, usize), b: &(u64, usize)| -> Ordering {
let ord = fold_cmp(a, b);
if reverse { ord.reverse() } else { ord }
};
if stable {
entries.sort_by(fold_cmp_rev);
} else {
entries.sort_unstable_by(fold_cmp_rev);
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
}
} else if is_numeric_only {
let reverse = gopts.reverse;
let stable = config.stable;
let mut entries: Vec<(u64, usize)> = if gopts.numeric {
let parse_numeric_inline = |i: usize, &(s, e): &(usize, usize)| -> (u64, usize) {
let line = &data[s..e];
match try_parse_integer(line) {
Some(v) => (int_to_sortable_u64(v), i),
None => (float_to_sortable_u64(parse_numeric_value(line)), i),
}
};
if num_lines > 10_000 {
offsets
.par_iter()
.enumerate()
.map(|(i, off)| parse_numeric_inline(i, off))
.collect()
} else {
offsets
.iter()
.enumerate()
.map(|(i, off)| parse_numeric_inline(i, off))
.collect()
}
} else {
if num_lines > 10_000 {
offsets
.par_iter()
.enumerate()
.map(|(i, &(s, e))| (parse_to_sortable_u64(&data[s..e], gopts), i))
.collect()
} else {
offsets
.iter()
.enumerate()
.map(|(i, &(s, e))| (parse_to_sortable_u64(&data[s..e], gopts), i))
.collect()
}
};
if entries.len() > 1 {
let is_sorted = if reverse {
entries.windows(2).all(|w| w[0].0 >= w[1].0)
} else {
entries.windows(2).all(|w| w[0].0 <= w[1].0)
};
if is_sorted {
if !config.unique && !config.zero_terminated && !has_cr {
if data.last() == Some(&b'\n') {
writer.write_all(data)?;
} else if !data.is_empty() {
writer.write_all(data)?;
writer.write_all(b"\n")?;
}
writer.flush()?;
return Ok(());
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
writer.flush()?;
return Ok(());
}
}
let n = entries.len();
if n > 256 {
let mut entries = radix_sort_numeric_entries(entries, data, &offsets, stable, false);
if reverse {
entries.reverse();
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
} else {
let dp_ns = data.as_ptr();
let cmp = |a: &(u64, usize), b: &(u64, usize)| -> Ordering {
let ord = a.0.cmp(&b.0);
if ord != Ordering::Equal {
return if reverse { ord.reverse() } else { ord };
}
if !stable {
let (sa, ea) = offsets[a.1];
let (sb, eb) = offsets[b.1];
unsafe {
std::slice::from_raw_parts(dp_ns.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp_ns.add(sb), eb - sb))
}
} else {
Ordering::Equal
}
};
if stable {
entries.sort_by(cmp);
} else {
entries.sort_unstable_by(cmp);
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
}
} else if is_single_key {
let key = &config.keys[0];
let opts = if key.opts.has_sort_type()
|| key.opts.ignore_case
|| key.opts.dictionary_order
|| key.opts.ignore_nonprinting
|| key.opts.ignore_leading_blanks
|| key.opts.reverse
{
&key.opts
} else {
gopts
};
let key_offs = pre_extract_key_offsets(
data,
&offsets,
key,
config.separator,
opts.ignore_leading_blanks,
config.zero_terminated,
);
let is_key_numeric = opts.numeric || opts.general_numeric || opts.human_numeric;
if is_key_numeric {
let is_gen = opts.general_numeric;
let is_pure_numeric = opts.numeric && !opts.general_numeric && !opts.human_numeric;
let reverse = opts.reverse;
let stable = config.stable;
let mut entries: Vec<(u64, usize)> = if is_pure_numeric {
let parse_entry = |i: usize, &(s, e): &(usize, usize)| -> (u64, usize) {
if s == e {
return (int_to_sortable_u64(0), i);
}
let line = &data[s..e];
match try_parse_integer(line) {
Some(v) => (int_to_sortable_u64(v), i),
None => (float_to_sortable_u64(parse_numeric_value(line)), i),
}
};
if num_lines > 10_000 {
key_offs
.par_iter()
.enumerate()
.map(|(i, ko)| parse_entry(i, ko))
.collect()
} else {
key_offs
.iter()
.enumerate()
.map(|(i, ko)| parse_entry(i, ko))
.collect()
}
} else {
let parse_entry = |i: usize, &(s, e): &(usize, usize)| {
let u = if s == e {
if is_gen {
float_to_sortable_u64(f64::NAN)
} else if opts.human_numeric {
human_numeric_to_sortable_u64(b"")
} else {
float_to_sortable_u64(0.0)
}
} else {
parse_to_sortable_u64(&data[s..e], opts)
};
(u, i)
};
if num_lines > 10_000 {
key_offs
.par_iter()
.enumerate()
.map(|(i, ko)| parse_entry(i, ko))
.collect()
} else {
key_offs
.iter()
.enumerate()
.map(|(i, ko)| parse_entry(i, ko))
.collect()
}
};
let n = entries.len();
if n > 256 {
let mut entries =
radix_sort_numeric_entries(entries, data, &offsets, stable, false);
if reverse {
entries.reverse();
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
} else {
let dp_skn = data.as_ptr();
let cmp = |a: &(u64, usize), b: &(u64, usize)| -> Ordering {
let ord = a.0.cmp(&b.0);
if ord != Ordering::Equal {
return if reverse { ord.reverse() } else { ord };
}
if !stable {
let (sa, ea) = offsets[a.1];
let (sb, eb) = offsets[b.1];
unsafe {
std::slice::from_raw_parts(dp_skn.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp_skn.add(sb), eb - sb))
}
} else {
Ordering::Equal
}
};
if stable {
entries.sort_by(cmp);
} else {
entries.sort_unstable_by(cmp);
}
write_sorted_entries(data, &offsets, &entries, config, &mut writer, terminator)?;
}
} else {
let stable = config.stable;
let reverse = opts.reverse;
let random_seed = config.random_seed;
let has_flags = opts.dictionary_order
|| opts.ignore_case
|| opts.ignore_nonprinting
|| opts.ignore_leading_blanks
|| opts.has_sort_type()
|| !is_c_locale();
if !has_flags {
if num_lines > 1 {
let mut is_sorted_fwd = true;
let mut is_sorted_rev = true;
let mut prev_pfx = if key_offs[0].0 < key_offs[0].1 {
line_prefix(data, key_offs[0].0, key_offs[0].1)
} else {
0u64
};
for i in 1..num_lines {
if !is_sorted_fwd && !is_sorted_rev {
break;
}
let (ks, ke) = key_offs[i];
let cur_pfx = if ks < ke {
line_prefix(data, ks, ke)
} else {
0u64
};
if is_sorted_fwd {
if cur_pfx < prev_pfx {
is_sorted_fwd = false;
} else if cur_pfx == prev_pfx {
let (ps, pe) = key_offs[i - 1];
let pk = &data[ps..pe];
let ck = &data[ks..ke];
if pk > ck {
is_sorted_fwd = false;
} else if !config.stable && pk == ck {
let (ls, le) = offsets[i - 1];
let (cs, ce) = offsets[i];
if data[ls..le] > data[cs..ce] {
is_sorted_fwd = false;
}
}
}
}
if is_sorted_rev {
if cur_pfx > prev_pfx {
is_sorted_rev = false;
} else if cur_pfx == prev_pfx {
let (ps, pe) = key_offs[i - 1];
let pk = &data[ps..pe];
let ck = &data[ks..ke];
if pk < ck {
is_sorted_rev = false;
} else if !config.stable && pk == ck {
let (ls, le) = offsets[i - 1];
let (cs, ce) = offsets[i];
if data[ls..le] < data[cs..ce] {
is_sorted_rev = false;
}
}
}
}
prev_pfx = cur_pfx;
}
let already_correct = (is_sorted_fwd && !reverse) || (is_sorted_rev && reverse);
let needs_reverse = (is_sorted_fwd && reverse) || (is_sorted_rev && !reverse);
if already_correct || needs_reverse {
let forward = already_correct;
if forward
&& !config.unique
&& !config.zero_terminated
&& memchr::memchr(b'\r', data).is_none()
{
if data.last() == Some(&b'\n') {
writer.write_all_direct(data)?;
} else if !data.is_empty() {
writer.write_all(data)?;
writer.write_all(b"\n")?;
}
writer.flush()?;
return Ok(());
}
let dp = data.as_ptr();
let term_byte = terminator[0];
let buf_cap = data.len() + num_lines + 1;
let mut buf: Vec<u8> = Vec::with_capacity(buf_cap);
let bptr = buf.as_mut_ptr();
let mut pos = 0usize;
let mut prev_idx: Option<usize> = None;
let iter: Box<dyn Iterator<Item = usize>> = if forward {
Box::new(0..num_lines)
} else {
Box::new((0..num_lines).rev())
};
for i in iter {
let (s, e) = offsets[i];
let len = e - s;
if config.unique {
let line = unsafe { std::slice::from_raw_parts(dp.add(s), len) };
let emit = match prev_idx {
Some(p) => {
let (ps, pe) = offsets[p];
let prev_line = unsafe {
std::slice::from_raw_parts(dp.add(ps), pe - ps)
};
compare_lines_for_dedup(prev_line, line, config)
!= Ordering::Equal
}
None => true,
};
if !emit {
continue;
}
prev_idx = Some(i);
}
unsafe {
std::ptr::copy_nonoverlapping(dp.add(s), bptr.add(pos), len);
*bptr.add(pos + len) = term_byte;
}
pos += len + 1;
}
unsafe {
buf.set_len(pos);
}
writer.write_all_direct(&buf)?;
writer.flush()?;
return Ok(());
}
}
type PackedEntry = (u64, u32, u32, u32);
let build_packed = |i: usize, &(ks, ke): &(usize, usize)| -> PackedEntry {
let pfx = if ks < ke {
line_prefix(data, ks, ke)
} else {
0u64
};
(pfx, ks as u32, ke as u32, i as u32)
};
let mut entries: Vec<PackedEntry> = if num_lines > 10_000 {
key_offs
.par_iter()
.enumerate()
.map(|(i, ko)| build_packed(i, ko))
.collect()
} else {
key_offs
.iter()
.enumerate()
.map(|(i, ko)| build_packed(i, ko))
.collect()
};
let data_addr = data.as_ptr() as usize;
let offsets_ptr = offsets.as_ptr() as usize;
let packed_cmp = |a: &PackedEntry, b: &PackedEntry| -> Ordering {
let ord = match a.0.cmp(&b.0) {
Ordering::Equal => {
let la = (a.2 - a.1) as usize;
let lb = (b.2 - b.1) as usize;
let skip = 8.min(la).min(lb);
unsafe {
let dp = data_addr as *const u8;
let pa = dp.add(a.1 as usize + skip);
let pb = dp.add(b.1 as usize + skip);
let rem_a = la - skip;
let rem_b = lb - skip;
let min_rem = rem_a.min(rem_b);
let mut i = 0usize;
while i + 8 <= min_rem {
let wa = u64::from_be_bytes(*(pa.add(i) as *const [u8; 8]));
let wb = u64::from_be_bytes(*(pb.add(i) as *const [u8; 8]));
if wa != wb {
return wa.cmp(&wb);
}
i += 8;
}
std::slice::from_raw_parts(pa.add(i), rem_a - i)
.cmp(std::slice::from_raw_parts(pb.add(i), rem_b - i))
}
}
ord => ord,
};
if ord != Ordering::Equal {
return if reverse { ord.reverse() } else { ord };
}
if !stable {
let off = offsets_ptr as *const (usize, usize);
let (la, ra) = unsafe { *off.add(a.3 as usize) };
let (lb, rb) = unsafe { *off.add(b.3 as usize) };
unsafe {
let dp = data_addr as *const u8;
std::slice::from_raw_parts(dp.add(la), ra - la)
.cmp(std::slice::from_raw_parts(dp.add(lb), rb - lb))
}
} else {
Ordering::Equal
}
};
if num_lines > 4096 && !reverse && !stable {
let n = entries.len();
let mut temp: Vec<PackedEntry> = Vec::with_capacity(n);
#[allow(clippy::uninit_vec)]
unsafe {
temp.set_len(n);
}
let mut cnts = [0u32; 256];
for ent in entries.iter() {
cnts[(ent.0 >> 56) as usize] += 1;
}
let mut bk_starts = [0usize; 257];
{
let mut s = 0;
for i in 0..256 {
bk_starts[i] = s;
s += cnts[i] as usize;
}
bk_starts[256] = s;
}
{
let mut wpos = bk_starts;
let tptr = temp.as_mut_ptr();
let sptr = entries.as_ptr();
for idx in 0..n {
let ent = unsafe { *sptr.add(idx) };
let b = (ent.0 >> 56) as usize;
unsafe {
*tptr.add(wpos[b]) = ent;
}
wpos[b] += 1;
}
}
entries = temp;
let entries_ptr = entries.as_mut_ptr() as usize;
let buckets: Vec<(usize, usize)> = (0..256)
.filter(|&i| bk_starts[i + 1] - bk_starts[i] > 1)
.map(|i| (bk_starts[i], bk_starts[i + 1]))
.collect();
let pk_cmp = |a: &PackedEntry, b: &PackedEntry| -> Ordering {
let ord = match a.0.cmp(&b.0) {
Ordering::Equal => {
let la = (a.2 - a.1) as usize;
let lb = (b.2 - b.1) as usize;
let skip = 8.min(la).min(lb);
unsafe {
let dp = data_addr as *const u8;
let pa = dp.add(a.1 as usize + skip);
let pb = dp.add(b.1 as usize + skip);
let rem_a = la - skip;
let rem_b = lb - skip;
let min_rem = rem_a.min(rem_b);
let mut i = 0usize;
while i + 8 <= min_rem {
let wa = u64::from_be_bytes(*(pa.add(i) as *const [u8; 8]));
let wb = u64::from_be_bytes(*(pb.add(i) as *const [u8; 8]));
if wa != wb {
return wa.cmp(&wb);
}
i += 8;
}
std::slice::from_raw_parts(pa.add(i), rem_a - i)
.cmp(std::slice::from_raw_parts(pb.add(i), rem_b - i))
}
}
ord => ord,
};
if ord != Ordering::Equal {
return ord;
}
let off = offsets_ptr as *const (usize, usize);
let (la, ra) = unsafe { *off.add(a.3 as usize) };
let (lb, rb) = unsafe { *off.add(b.3 as usize) };
unsafe {
let dp = data_addr as *const u8;
std::slice::from_raw_parts(dp.add(la), ra - la)
.cmp(std::slice::from_raw_parts(dp.add(lb), rb - lb))
}
};
buckets.into_par_iter().for_each(|(lo, hi)| {
let bsize = hi - lo;
let group = unsafe {
std::slice::from_raw_parts_mut(
(entries_ptr as *mut PackedEntry).add(lo),
bsize,
)
};
if bsize <= 64 {
group.sort_unstable_by(pk_cmp);
return;
}
let mut cnts2 = [0u32; 256];
for e in group.iter() {
cnts2[((e.0 >> 48) & 0xFF) as usize] += 1;
}
let mut starts2 = [0usize; 257];
{
let mut s2 = 0;
for i in 0..256 {
starts2[i] = s2;
s2 += cnts2[i] as usize;
}
starts2[256] = s2;
}
let mut temp2: Vec<PackedEntry> = Vec::with_capacity(bsize);
#[allow(clippy::uninit_vec)]
unsafe {
temp2.set_len(bsize);
}
{
let mut wpos2 = starts2;
for &ent in group.iter() {
let b = ((ent.0 >> 48) & 0xFF) as usize;
temp2[wpos2[b]] = ent;
wpos2[b] += 1;
}
}
group.copy_from_slice(&temp2);
drop(temp2);
for i in 0..256 {
let sub_sz = starts2[i + 1] - starts2[i];
if sub_sz > 1 {
group[starts2[i]..starts2[i + 1]].sort_unstable_by(pk_cmp);
}
}
});
} else if num_lines > 10_000 {
entries.par_sort_unstable_by(packed_cmp);
} else if stable {
entries.sort_by(packed_cmp);
} else {
entries.sort_unstable_by(packed_cmp);
}
{
let dp = data.as_ptr();
let n = entries.len();
let term_byte = terminator[0];
if config.unique {
let buf_cap = data.len() + n + 1;
let mut buf: Vec<u8> = Vec::with_capacity(buf_cap);
let bptr = buf.as_mut_ptr();
let mut pos = 0usize;
let mut prev: Option<u32> = None;
for j in 0..n {
let idx = if reverse { n - 1 - j } else { j };
let ent = &entries[idx];
let li = ent.3 as usize;
let (s, e) = offsets[li];
let len = e - s;
let should_output = match prev {
Some(p) => {
let pi = p as usize;
let (ps, pe) = offsets[pi];
let prev_line =
unsafe { std::slice::from_raw_parts(dp.add(ps), pe - ps) };
let line =
unsafe { std::slice::from_raw_parts(dp.add(s), len) };
compare_lines_for_dedup(prev_line, line, config)
!= Ordering::Equal
}
None => true,
};
if should_output {
unsafe {
std::ptr::copy_nonoverlapping(dp.add(s), bptr.add(pos), len);
*bptr.add(pos + len) = term_byte;
}
pos += len + 1;
prev = Some(ent.3);
}
}
unsafe {
buf.set_len(pos);
}
writer.write_all_direct(&buf)?;
} else {
let total_size = if data.last() == Some(&term_byte) {
data.len()
} else {
data.len() + 1
};
let mut buf: Vec<u8> = Vec::with_capacity(total_size);
let bptr = buf.as_mut_ptr();
let mut pos = 0usize;
for j in 0..n {
let actual = if reverse { n - 1 - j } else { j };
if j + 16 < n {
let ahead_idx = if reverse { n - 1 - (j + 16) } else { j + 16 };
let (ps, _) = offsets[entries[ahead_idx].3 as usize];
prefetch_read(unsafe { dp.add(ps) });
}
let ent = &entries[actual];
let (s, e) = offsets[ent.3 as usize];
let len = e - s;
unsafe {
std::ptr::copy_nonoverlapping(dp.add(s), bptr.add(pos), len);
*bptr.add(pos + len) = term_byte;
}
pos += len + 1;
}
unsafe {
buf.set_len(pos);
}
writer.write_all_direct(&buf)?;
}
}
} else {
let is_locale_only = !opts.dictionary_order
&& !opts.ignore_case
&& !opts.ignore_nonprinting
&& !opts.ignore_leading_blanks
&& !opts.has_sort_type()
&& !is_c_locale();
let has_null_keys = is_locale_only
&& key_offs
.iter()
.any(|&(sa, ea)| memchr::memchr(0, &data[sa..ea]).is_some());
let xfrm_keys: Option<Vec<Vec<u8>>> = if is_locale_only && !has_null_keys {
let keys: Vec<Option<Vec<u8>>> = if num_lines > 10_000 {
key_offs
.par_iter()
.map(|&(sa, ea)| compute_xfrm_key(&data[sa..ea]))
.collect()
} else {
let mut c_buf = vec![0u8; 512];
key_offs
.iter()
.map(|&(sa, ea)| compute_xfrm_key_reuse(&data[sa..ea], &mut c_buf))
.collect()
};
if keys.iter().all(|k| k.is_some()) {
Some(keys.into_iter().map(|k| k.unwrap()).collect())
} else {
None
}
} else {
None
};
if let Some(xfrm_keys) = xfrm_keys {
let mut indices: Vec<usize> = (0..num_lines).collect();
let dp_sk = data.as_ptr() as usize;
do_sort(&mut indices, stable, |&a, &b| {
let ka = xfrm_keys[a].as_slice();
let kb = xfrm_keys[b].as_slice();
let ord = ka.cmp(kb);
let ord = if reverse { ord.reverse() } else { ord };
if ord == Ordering::Equal && !stable {
let dp = dp_sk as *const u8;
let (la, ra) = offsets[a];
let (lb, rb) = offsets[b];
unsafe {
std::slice::from_raw_parts(dp.add(la), ra - la)
.cmp(std::slice::from_raw_parts(dp.add(lb), rb - lb))
}
} else {
ord
}
});
write_sorted_output(data, &offsets, &indices, config, &mut writer, terminator)?;
} else {
let mut indices: Vec<usize> = (0..num_lines).collect();
let (cmp_fn, needs_blank, needs_reverse) = select_comparator(opts, random_seed);
let dp_sk = data.as_ptr() as usize;
do_sort(&mut indices, stable, |&a, &b| {
let dp = dp_sk as *const u8;
let (sa, ea) = key_offs[a];
let (sb, eb) = key_offs[b];
let ka = if sa == ea {
&[] as &[u8]
} else if needs_blank {
skip_leading_blanks(unsafe {
std::slice::from_raw_parts(dp.add(sa), ea - sa)
})
} else {
unsafe { std::slice::from_raw_parts(dp.add(sa), ea - sa) }
};
let kb = if sb == eb {
&[] as &[u8]
} else if needs_blank {
skip_leading_blanks(unsafe {
std::slice::from_raw_parts(dp.add(sb), eb - sb)
})
} else {
unsafe { std::slice::from_raw_parts(dp.add(sb), eb - sb) }
};
let ord = cmp_fn(ka, kb);
let ord = if needs_reverse { ord.reverse() } else { ord };
if ord == Ordering::Equal && !stable {
let (la, ra) = offsets[a];
let (lb, rb) = offsets[b];
unsafe {
std::slice::from_raw_parts(dp.add(la), ra - la)
.cmp(std::slice::from_raw_parts(dp.add(lb), rb - lb))
}
} else {
ord
}
});
write_sorted_output(data, &offsets, &indices, config, &mut writer, terminator)?;
}
}
}
} else if config.keys.len() > 1 {
let mut indices: Vec<usize> = (0..num_lines).collect();
let num_keys = config.keys.len();
let stable = config.stable;
let random_seed = config.random_seed;
let keys = &config.keys;
let global_opts = &config.global_opts;
let comparators: Vec<_> = keys
.iter()
.map(|key| {
let opts = if key.opts.has_sort_type()
|| key.opts.ignore_case
|| key.opts.dictionary_order
|| key.opts.ignore_nonprinting
|| key.opts.ignore_leading_blanks
|| key.opts.reverse
{
&key.opts
} else {
global_opts
};
select_comparator(opts, random_seed)
})
.collect();
let per_key_offs: Vec<Vec<(usize, usize)>> = if num_lines > 10_000 {
config
.keys
.par_iter()
.enumerate()
.map(|(ki, key)| {
let (_, needs_blank, _) = comparators[ki];
pre_extract_key_offsets(
data,
&offsets,
key,
config.separator,
needs_blank,
config.zero_terminated,
)
})
.collect()
} else {
config
.keys
.iter()
.enumerate()
.map(|(ki, key)| {
let (_, needs_blank, _) = comparators[ki];
pre_extract_key_offsets(
data,
&offsets,
key,
config.separator,
needs_blank,
config.zero_terminated,
)
})
.collect()
};
let is_not_c = !is_c_locale();
let key_needs_locale: Vec<bool> = keys
.iter()
.map(|key| {
let opts = if key.opts.has_sort_type()
|| key.opts.ignore_case
|| key.opts.dictionary_order
|| key.opts.ignore_nonprinting
|| key.opts.ignore_leading_blanks
|| key.opts.reverse
{
&key.opts
} else {
global_opts
};
is_not_c
&& !opts.has_sort_type()
&& !opts.dictionary_order
&& !opts.ignore_case
&& !opts.ignore_nonprinting
&& !opts.ignore_leading_blanks
})
.collect();
let key_has_nulls: Vec<bool> = (0..num_keys)
.map(|ki| {
if !key_needs_locale[ki] {
return false;
}
per_key_offs[ki]
.iter()
.any(|&(sa, ea)| memchr::memchr(0, &data[sa..ea]).is_some())
})
.collect();
let per_key_xfrm: Vec<Option<Vec<Vec<u8>>>> = (0..num_keys)
.map(|ki| {
if !key_needs_locale[ki] || key_has_nulls[ki] {
return None;
}
let ko = &per_key_offs[ki];
let keys: Vec<Option<Vec<u8>>> = if num_lines > 10_000 {
ko.par_iter()
.map(|&(sa, ea)| compute_xfrm_key(&data[sa..ea]))
.collect()
} else {
let mut c_buf = vec![0u8; 512];
ko.iter()
.map(|&(sa, ea)| compute_xfrm_key_reuse(&data[sa..ea], &mut c_buf))
.collect()
};
if keys.iter().all(|k| k.is_some()) {
Some(keys.into_iter().map(|k| k.unwrap()).collect())
} else {
None
}
})
.collect();
let mut flat_offs: Vec<(usize, usize)> = Vec::with_capacity(num_lines * num_keys);
for li in 0..num_lines {
for (ki, key_offs) in per_key_offs.iter().enumerate() {
let (s, e) = key_offs[li];
if s == e || !comparators[ki].1 {
flat_offs.push((s, e));
} else {
let slice = &data[s..e];
let trimmed = skip_leading_blanks(slice);
let new_s = s + (slice.len() - trimmed.len());
flat_offs.push((new_s, e));
}
}
}
drop(per_key_offs);
let dp_mk = data.as_ptr() as usize;
let flat_ptr_usize = flat_offs.as_ptr() as usize;
do_sort(&mut indices, stable, |&a, &b| {
let dp = dp_mk as *const u8;
let fp = flat_ptr_usize as *const (usize, usize);
let base_a = a * num_keys;
let base_b = b * num_keys;
for (ki, &(cmp_fn, _needs_blank, needs_reverse)) in comparators.iter().enumerate() {
let result = if let Some(ref xfrm) = per_key_xfrm[ki] {
xfrm[a].as_slice().cmp(xfrm[b].as_slice())
} else {
let (sa, ea) = unsafe { *fp.add(base_a + ki) };
let (sb, eb) = unsafe { *fp.add(base_b + ki) };
let ka = if sa == ea {
&[] as &[u8]
} else {
unsafe { std::slice::from_raw_parts(dp.add(sa), ea - sa) }
};
let kb = if sb == eb {
&[] as &[u8]
} else {
unsafe { std::slice::from_raw_parts(dp.add(sb), eb - sb) }
};
cmp_fn(ka, kb)
};
let result = if needs_reverse {
result.reverse()
} else {
result
};
if result != Ordering::Equal {
return result;
}
}
if !stable {
let (sa, ea) = offsets[a];
let (sb, eb) = offsets[b];
unsafe {
std::slice::from_raw_parts(dp.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp.add(sb), eb - sb))
}
} else {
Ordering::Equal
}
});
write_sorted_output(data, &offsets, &indices, config, &mut writer, terminator)?;
} else if !config.keys.is_empty() {
let mut indices: Vec<usize> = (0..num_lines).collect();
let stable = config.stable;
let random_seed = config.random_seed;
let keys = &config.keys;
let global_opts = &config.global_opts;
let comparators: Vec<_> = keys
.iter()
.map(|key| {
let opts = if key.opts.has_any_option() {
&key.opts
} else {
global_opts
};
select_comparator(opts, random_seed)
})
.collect();
let dp_gen_key = data.as_ptr() as usize;
do_sort(&mut indices, stable, |&a, &b| {
let dp = dp_gen_key as *const u8;
let (sa, ea) = offsets[a];
let (sb, eb) = offsets[b];
let la = unsafe { std::slice::from_raw_parts(dp.add(sa), ea - sa) };
let lb = unsafe { std::slice::from_raw_parts(dp.add(sb), eb - sb) };
for (ki, &(cmp_fn, needs_blank, needs_reverse)) in comparators.iter().enumerate() {
let ka = extract_key_z(
la,
&keys[ki],
config.separator,
needs_blank,
config.zero_terminated,
);
let kb = extract_key_z(
lb,
&keys[ki],
config.separator,
needs_blank,
config.zero_terminated,
);
let ka = if needs_blank {
skip_leading_blanks(ka)
} else {
ka
};
let kb = if needs_blank {
skip_leading_blanks(kb)
} else {
kb
};
let result = cmp_fn(ka, kb);
let result = if needs_reverse {
result.reverse()
} else {
result
};
if result != Ordering::Equal {
return result;
}
}
if !stable { la.cmp(lb) } else { Ordering::Equal }
});
write_sorted_output(data, &offsets, &indices, config, &mut writer, terminator)?;
} else {
let mut indices: Vec<usize> = (0..num_lines).collect();
let (cmp_fn, needs_blank, needs_reverse) =
select_comparator(&config.global_opts, config.random_seed);
let stable = config.stable;
let dp_addr = data.as_ptr() as usize;
do_sort(&mut indices, stable, |&a, &b| {
let (sa, ea) = offsets[a];
let (sb, eb) = offsets[b];
let dp = dp_addr as *const u8;
let la = unsafe { std::slice::from_raw_parts(dp.add(sa), ea - sa) };
let lb = unsafe { std::slice::from_raw_parts(dp.add(sb), eb - sb) };
let la = if needs_blank {
skip_leading_blanks(la)
} else {
la
};
let lb = if needs_blank {
skip_leading_blanks(lb)
} else {
lb
};
let ord = cmp_fn(la, lb);
let ord = if needs_reverse { ord.reverse() } else { ord };
if ord == Ordering::Equal && !stable {
unsafe {
std::slice::from_raw_parts(dp.add(sa), ea - sa)
.cmp(std::slice::from_raw_parts(dp.add(sb), eb - sb))
}
} else {
ord
}
});
write_sorted_output(data, &offsets, &indices, config, &mut writer, terminator)?;
}
writer.flush()?;
Ok(())
}
pub fn parse_buffer_size(s: &str) -> Result<usize, String> {
let s = s.trim();
if s.is_empty() {
return Err("empty buffer size".to_string());
}
let (num_part, suffix) = if s.ends_with(|c: char| c.is_ascii_alphabetic()) {
let (n, s) = s.split_at(s.len() - 1);
(n, s.chars().next())
} else {
(s, None)
};
let base: usize = num_part
.parse()
.map_err(|_| format!("invalid buffer size: {}", s))?;
let multiplier = match suffix {
Some('K') | Some('k') => 1024,
Some('M') | Some('m') => 1024 * 1024,
Some('G') | Some('g') => 1024 * 1024 * 1024,
Some('T') | Some('t') => 1024usize.pow(4),
Some('b') => 512,
Some(c) => return Err(format!("invalid suffix '{}' in buffer size", c)),
None => 1,
};
Ok(base * multiplier)
}