Skip to content

Commit c544009

Browse files
authored
Merge pull request #8643 from RenjiSann/perf-cksum
cksum: improve performance (Closes: #8573)
2 parents e489615 + 2c5409b commit c544009

File tree

6 files changed

+220
-52
lines changed

6 files changed

+220
-52
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 }
@@ -158,6 +159,7 @@ sum = [
158159
"blake3",
159160
"sm3",
160161
"crc32fast",
162+
"crc-fast",
161163
]
162164
update-control = ["parser"]
163165
utf8 = []

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

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

11541154
pub fn digest_reader<T: Read>(
11551155
digest: &mut Box<dyn Digest>,
1156-
reader: &mut BufReader<T>,
1156+
reader: &mut T,
11571157
binary: bool,
11581158
output_bits: usize,
11591159
) -> io::Result<(String, usize)> {

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

Lines changed: 156 additions & 51 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,60 @@ 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;
127-
128125
pub struct Crc {
129-
state: u32,
126+
digest: crc_fast::Digest,
130127
size: usize,
131-
crc_table: [u32; CRC_TABLE_LEN],
132128
}
133-
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;
151129

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];
130+
impl Crc {
131+
/// POSIX cksum SIMD configuration for crc-fast
132+
/// This uses SIMD instructions (PCLMULQDQ) for fast CRC computation
133+
fn get_posix_cksum_params() -> crc_fast::CrcParams {
134+
crc_fast::CrcParams::new(
135+
"CRC-32/CKSUM", // Name
136+
32, // Width
137+
0x04c11db7, // Polynomial
138+
0x00000000, // Initial CRC value: 0 (not 0xffffffff)
139+
false, // No input reflection (refin)
140+
0xffffffff, // XOR output with 0xffffffff (xorout)
141+
0, // Check value (not used)
142+
)
166143
}
167144
}
168145

169146
impl Digest for Crc {
170147
fn new() -> Self {
171148
Self {
172-
state: 0,
149+
digest: crc_fast::Digest::new_with_params(Self::get_posix_cksum_params()),
173150
size: 0,
174-
crc_table: Self::generate_crc_table(),
175151
}
176152
}
177153

178154
fn hash_update(&mut self, input: &[u8]) {
179-
for &elt in input {
180-
self.update(elt);
181-
}
155+
self.digest.update(input);
182156
self.size += input.len();
183157
}
184158

185159
fn hash_finalize(&mut self, out: &mut [u8]) {
160+
// Add the size at the end of the buffer.
186161
let mut sz = self.size;
187-
while sz != 0 {
188-
self.update(sz as u8);
162+
while sz > 0 {
163+
self.digest.update(&[sz as u8]);
189164
sz >>= 8;
190165
}
191-
self.state = !self.state;
192-
out.copy_from_slice(&self.state.to_ne_bytes());
166+
167+
out.copy_from_slice(&self.digest.finalize().to_ne_bytes());
193168
}
194169

195170
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)
171+
let mut out: [u8; 8] = [0; 8];
172+
self.hash_finalize(&mut out);
173+
u64::from_ne_bytes(out).to_string()
199174
}
200175

201176
fn reset(&mut self) {
202-
*self = Self::new();
177+
self.digest.reset();
178+
self.size = 0;
203179
}
204180

205181
fn output_bits(&self) -> usize {
@@ -529,4 +505,133 @@ mod tests {
529505

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

0 commit comments

Comments
 (0)