Skip to content

Commit 81f0a2b

Browse files
sylvestreRenjiSann
andcommitted
cksum: improve performance (Closes: #8573)
Co-authored-by: Sylvestre Ledru <[email protected]> Co-authored-by: Dorian Peron <[email protected]>
1 parent 8357431 commit 81f0a2b

File tree

6 files changed

+217
-61
lines changed

6 files changed

+217
-61
lines changed

Cargo.lock

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ blake2b_simd = "1.0.2"
391391
blake3 = "1.5.1"
392392
sm3 = "0.4.2"
393393
crc32fast = "1.4.2"
394+
crc-fast = "1.5.0"
394395
digest = "0.10.7"
395396

396397
# Fluent dependencies

fuzz/Cargo.lock

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uucore/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ blake2b_simd = { workspace = true, optional = true }
5858
blake3 = { workspace = true, optional = true }
5959
sm3 = { workspace = true, optional = true }
6060
crc32fast = { workspace = true, optional = true }
61+
crc-fast = { workspace = true, optional = true }
6162
bigdecimal = { workspace = true, optional = true }
6263
num-traits = { workspace = true, optional = true }
6364
selinux = { workspace = true, optional = true }
@@ -152,6 +153,7 @@ sum = [
152153
"blake3",
153154
"sm3",
154155
"crc32fast",
156+
"crc-fast",
155157
]
156158
update-control = ["parser"]
157159
utf8 = []

src/uucore/src/lib/features/checksum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1155,7 +1155,7 @@ where
11551155

11561156
pub fn digest_reader<T: Read>(
11571157
digest: &mut Box<dyn Digest>,
1158-
reader: &mut BufReader<T>,
1158+
reader: &mut T,
11591159
binary: bool,
11601160
output_bits: usize,
11611161
) -> io::Result<(String, usize)> {

src/uucore/src/lib/features/sum.rs

Lines changed: 153 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore memmem algo
6+
// spell-checker:ignore memmem algo PCLMULQDQ refin xorout
77

88
//! Implementations of digest functions, like md5 and sha1.
99
//!
@@ -122,84 +122,48 @@ impl Digest for Sm3 {
122122
}
123123
}
124124

125-
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
126-
const CRC_TABLE_LEN: usize = 256;
125+
pub struct Crc(crc_fast::Digest);
127126

128-
pub struct Crc {
129-
state: u32,
130-
size: usize,
131-
crc_table: [u32; CRC_TABLE_LEN],
132-
}
133127
impl Crc {
134-
fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
135-
let mut table = [0; CRC_TABLE_LEN];
136-
137-
for (i, elt) in table.iter_mut().enumerate().take(CRC_TABLE_LEN) {
138-
*elt = Self::crc_entry(i as u8);
139-
}
140-
141-
table
142-
}
143-
fn crc_entry(input: u8) -> u32 {
144-
let mut crc = (input as u32) << 24;
145-
146-
let mut i = 0;
147-
while i < 8 {
148-
let if_condition = crc & 0x8000_0000;
149-
let if_body = (crc << 1) ^ 0x04c1_1db7;
150-
let else_body = crc << 1;
151-
152-
// NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
153-
// ops
154-
let condition_table = [else_body, if_body];
155-
156-
crc = condition_table[(if_condition != 0) as usize];
157-
i += 1;
158-
}
159-
160-
crc
161-
}
162-
163-
fn update(&mut self, input: u8) {
164-
self.state = (self.state << 8)
165-
^ self.crc_table[((self.state >> 24) as usize ^ input as usize) & 0xFF];
128+
/// POSIX cksum SIMD configuration for crc-fast
129+
/// This uses SIMD instructions (PCLMULQDQ) for fast CRC computation
130+
fn get_posix_cksum_params() -> crc_fast::CrcParams {
131+
crc_fast::CrcParams::new(
132+
"CRC-32/CKSUM", // Name
133+
32, // Width
134+
0x04c11db7, // Polynomial
135+
0x00000000, // Initial CRC value: 0 (not 0xffffffff)
136+
false, // No input reflection (refin)
137+
0xffffffff, // XOR output with 0xffffffff (xorout)
138+
0, // Check value (not used)
139+
)
166140
}
167141
}
168142

169143
impl Digest for Crc {
170144
fn new() -> Self {
171-
Self {
172-
state: 0,
173-
size: 0,
174-
crc_table: Self::generate_crc_table(),
175-
}
145+
Self(crc_fast::Digest::new_with_params(
146+
Self::get_posix_cksum_params(),
147+
))
176148
}
177149

178150
fn hash_update(&mut self, input: &[u8]) {
179-
for &elt in input {
180-
self.update(elt);
181-
}
182-
self.size += input.len();
151+
self.0.update(input);
183152
}
184153

185154
fn hash_finalize(&mut self, out: &mut [u8]) {
186-
let mut sz = self.size;
187-
while sz != 0 {
188-
self.update(sz as u8);
189-
sz >>= 8;
190-
}
191-
self.state = !self.state;
192-
out.copy_from_slice(&self.state.to_ne_bytes());
155+
let xout = self.0.finalize();
156+
out.copy_from_slice(&xout.to_ne_bytes());
193157
}
194158

195159
fn result_str(&mut self) -> String {
196-
let mut _out: Vec<u8> = vec![0; 4];
197-
self.hash_finalize(&mut _out);
198-
format!("{}", self.state)
160+
let mut out: [u8; 8] = [0; 8];
161+
self.hash_finalize(&mut out);
162+
u64::from_ne_bytes(out).to_string()
199163
}
200164

201165
fn reset(&mut self) {
202-
*self = Self::new();
166+
self.0.reset();
203167
}
204168

205169
fn output_bits(&self) -> usize {
@@ -529,4 +493,133 @@ mod tests {
529493

530494
assert_eq!(result_crlf, result_lf);
531495
}
496+
497+
use super::{Crc, Digest};
498+
499+
#[test]
500+
fn test_crc_basic_functionality() {
501+
// Test that our CRC implementation works with basic functionality
502+
let mut crc1 = Crc::new();
503+
let mut crc2 = Crc::new();
504+
505+
// Same input should give same output
506+
crc1.hash_update(b"test");
507+
crc2.hash_update(b"test");
508+
509+
let mut out1 = [0u8; 4];
510+
let mut out2 = [0u8; 4];
511+
crc1.hash_finalize(&mut out1);
512+
crc2.hash_finalize(&mut out2);
513+
514+
assert_eq!(out1, out2);
515+
}
516+
517+
#[test]
518+
fn test_crc_digest_basic() {
519+
let mut crc = Crc::new();
520+
521+
// Test empty input
522+
let mut output = [0u8; 4];
523+
crc.hash_finalize(&mut output);
524+
let empty_result = u32::from_ne_bytes(output);
525+
526+
// Reset and test with "test" string
527+
crc.reset();
528+
crc.hash_update(b"test");
529+
crc.hash_finalize(&mut output);
530+
let test_result = u32::from_ne_bytes(output);
531+
532+
// The result should be different for different inputs
533+
assert_ne!(empty_result, test_result);
534+
535+
// Test known value: "test" should give 3076352578
536+
assert_eq!(test_result, 3076352578);
537+
}
538+
539+
#[test]
540+
fn test_crc_digest_incremental() {
541+
let mut crc1 = Crc::new();
542+
let mut crc2 = Crc::new();
543+
544+
// Test that processing in chunks gives same result as all at once
545+
let data = b"Hello, World! This is a test string for CRC computation.";
546+
547+
// Process all at once
548+
crc1.hash_update(data);
549+
let mut output1 = [0u8; 4];
550+
crc1.hash_finalize(&mut output1);
551+
552+
// Process in chunks
553+
crc2.hash_update(&data[0..10]);
554+
crc2.hash_update(&data[10..30]);
555+
crc2.hash_update(&data[30..]);
556+
let mut output2 = [0u8; 4];
557+
crc2.hash_finalize(&mut output2);
558+
559+
assert_eq!(output1, output2);
560+
}
561+
562+
#[test]
563+
fn test_crc_slice8_vs_single_byte() {
564+
// Test that our optimized slice-by-8 gives same results as byte-by-byte
565+
let test_data = b"This is a longer test string to verify slice-by-8 optimization works correctly with various data sizes including remainders.";
566+
567+
let mut crc_optimized = Crc::new();
568+
crc_optimized.hash_update(test_data);
569+
let mut output_opt = [0u8; 4];
570+
crc_optimized.hash_finalize(&mut output_opt);
571+
572+
// Create a reference implementation using hash_update
573+
let mut crc_reference = Crc::new();
574+
for &byte in test_data {
575+
crc_reference.hash_update(&[byte]);
576+
}
577+
let mut output_ref = [0u8; 4];
578+
crc_reference.hash_finalize(&mut output_ref);
579+
580+
assert_eq!(output_opt, output_ref);
581+
}
582+
583+
#[test]
584+
fn test_crc_known_values() {
585+
// Test against our CRC implementation values
586+
// Note: These are the correct values for our POSIX cksum implementation
587+
let test_cases = [
588+
("", 4294967295u32),
589+
("a", 1220704766u32),
590+
("abc", 1219131554u32),
591+
];
592+
593+
for (input, expected) in test_cases {
594+
let mut crc = Crc::new();
595+
crc.hash_update(input.as_bytes());
596+
let mut output = [0u8; 4];
597+
crc.hash_finalize(&mut output);
598+
let result = u32::from_ne_bytes(output);
599+
600+
assert_eq!(result, expected, "CRC mismatch for input: '{}'", input);
601+
}
602+
}
603+
604+
#[test]
605+
fn test_crc_hash_update_edge_cases() {
606+
let mut crc = Crc::new();
607+
608+
// Test with data that's not a multiple of 8 bytes
609+
let data7 = b"1234567"; // 7 bytes
610+
crc.hash_update(data7);
611+
612+
let data9 = b"123456789"; // 9 bytes
613+
let mut crc2 = Crc::new();
614+
crc2.hash_update(data9);
615+
616+
// Should not panic and should produce valid results
617+
let mut out1 = [0u8; 4];
618+
let mut out2 = [0u8; 4];
619+
crc.hash_finalize(&mut out1);
620+
crc2.hash_finalize(&mut out2);
621+
622+
// Results should be different for different inputs
623+
assert_ne!(out1, out2);
624+
}
532625
}

0 commit comments

Comments
 (0)