Skip to content

Commit 54959f3

Browse files
committed
tr: improve performances (Closes: uutils#8439)
1 parent d74d2e5 commit 54959f3

1 file changed

Lines changed: 51 additions & 37 deletions

File tree

src/uu/tr/src/operation.rs

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ use nom::{
1717
};
1818
use std::{
1919
char,
20-
collections::{HashMap, HashSet},
2120
error::Error,
2221
fmt::{Debug, Display},
2322
io::{BufRead, Write},
24-
ops::Not,
2523
};
2624
use uucore::error::{FromIo, UError, UResult};
2725
use uucore::translate;
@@ -583,42 +581,58 @@ impl<A: SymbolTranslator, B: SymbolTranslator> SymbolTranslator for ChainedSymbo
583581
}
584582
}
585583

584+
/// Convert a set of bytes to a 256-element bitmap for O(1) lookup
585+
fn set_to_bitmap(set: &[u8]) -> [bool; 256] {
586+
let mut bitmap = [false; 256];
587+
for &byte in set {
588+
bitmap[byte as usize] = true;
589+
}
590+
bitmap
591+
}
592+
586593
#[derive(Debug)]
587594
pub struct DeleteOperation {
588-
set: Vec<u8>,
595+
delete_table: [bool; 256],
589596
}
590597

591598
impl DeleteOperation {
592599
pub fn new(set: Vec<u8>) -> Self {
593-
Self { set }
600+
Self {
601+
delete_table: set_to_bitmap(&set),
602+
}
594603
}
595604
}
596605

597606
impl SymbolTranslator for DeleteOperation {
598607
fn translate(&mut self, current: u8) -> Option<u8> {
599-
// keep if not present in the set
600-
self.set.contains(&current).not().then_some(current)
608+
// keep if not present in the delete set
609+
(!self.delete_table[current as usize]).then_some(current)
601610
}
602611
}
603612

604613
#[derive(Debug)]
605614
pub struct TranslateOperation {
606-
translation_map: HashMap<u8, u8>,
615+
translation_table: [u8; 256],
607616
}
608617

609618
impl TranslateOperation {
610619
pub fn new(set1: Vec<u8>, set2: Vec<u8>) -> Result<Self, BadSequence> {
620+
// Initialize translation table with identity mapping
621+
let mut translation_table = std::array::from_fn(|i| i as u8);
622+
611623
if let Some(fallback) = set2.last().copied() {
612-
Ok(Self {
613-
translation_map: set1
614-
.into_iter()
615-
.zip(set2.into_iter().chain(std::iter::repeat(fallback)))
616-
.collect::<HashMap<_, _>>(),
617-
})
624+
// Apply translations from set1 to set2
625+
for (from, to) in set1
626+
.into_iter()
627+
.zip(set2.into_iter().chain(std::iter::repeat(fallback)))
628+
{
629+
translation_table[from as usize] = to;
630+
}
631+
632+
Ok(Self { translation_table })
618633
} else if set1.is_empty() && set2.is_empty() {
619-
Ok(Self {
620-
translation_map: HashMap::new(),
621-
})
634+
// Identity mapping for empty sets
635+
Ok(Self { translation_table })
622636
} else {
623637
Err(BadSequence::EmptySet2WhenNotTruncatingSet1)
624638
}
@@ -627,33 +641,28 @@ impl TranslateOperation {
627641

628642
impl SymbolTranslator for TranslateOperation {
629643
fn translate(&mut self, current: u8) -> Option<u8> {
630-
Some(
631-
self.translation_map
632-
.get(&current)
633-
.copied()
634-
.unwrap_or(current),
635-
)
644+
Some(self.translation_table[current as usize])
636645
}
637646
}
638647

639648
#[derive(Debug, Clone)]
640649
pub struct SqueezeOperation {
641-
set1: HashSet<u8>,
650+
squeeze_table: [bool; 256],
642651
previous: Option<u8>,
643652
}
644653

645654
impl SqueezeOperation {
646655
pub fn new(set1: Vec<u8>) -> Self {
647656
Self {
648-
set1: set1.into_iter().collect(),
657+
squeeze_table: set_to_bitmap(&set1),
649658
previous: None,
650659
}
651660
}
652661
}
653662

654663
impl SymbolTranslator for SqueezeOperation {
655664
fn translate(&mut self, current: u8) -> Option<u8> {
656-
let next = if self.set1.contains(&current) {
665+
let next = if self.squeeze_table[current as usize] {
657666
match self.previous {
658667
Some(v) if v == current => None,
659668
_ => Some(current),
@@ -672,19 +681,26 @@ where
672681
R: BufRead,
673682
W: Write,
674683
{
675-
let mut buf = [0; 8192];
676-
let mut output_buf = Vec::new();
684+
const BUFFER_SIZE: usize = 32768; // Large buffer for better throughput
685+
let mut buf = [0; BUFFER_SIZE];
686+
let mut output_buf = Vec::with_capacity(buf.len());
687+
688+
loop {
689+
let length = match input.read(&mut buf[..]) {
690+
Ok(0) => break, // EOF reached
691+
Ok(len) => len,
692+
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
693+
Err(e) => return Err(e.map_err_context(|| translate!("tr-error-read-error"))),
694+
};
677695

678-
while let Ok(length) = input.read(&mut buf[..]) {
679-
if length == 0 {
680-
break; // EOF reached
696+
// Process the buffer and collect translated chars to output
697+
output_buf.clear();
698+
for &byte in &buf[..length] {
699+
if let Some(translated) = translator.translate(byte) {
700+
output_buf.push(translated);
701+
}
681702
}
682703

683-
let filtered = buf[..length]
684-
.iter()
685-
.filter_map(|&c| translator.translate(c));
686-
output_buf.extend(filtered);
687-
688704
#[cfg(not(target_os = "windows"))]
689705
output
690706
.write_all(&output_buf)
@@ -699,8 +715,6 @@ where
699715
return Err(err.map_err_context(|| translate!("tr-error-write-error")));
700716
}
701717
}
702-
703-
output_buf.clear();
704718
}
705719

706720
Ok(())

0 commit comments

Comments
 (0)