Skip to content

Commit 80be0b9

Browse files
authored
Merge pull request #7 from wiseaidev/fix-perf
feat: refactor, add benchmark && unit tests, improve perf, use safe operations
2 parents 44bcd84 + 2fee06d commit 80be0b9

10 files changed

+552
-463
lines changed

.bumpversion.toml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[bumpversion]
2+
current_version = 0.0.4
3+
4+
[bumpversion:file:Cargo.toml]
5+
search = version = "{current_version}"
6+
replace = version = "{new_version}"
7+
8+
[bumpversion:file:README.md]
9+
search = crc32-v2 = "{current_version}"
10+
replace = crc32-v2 = "{new_version}"

Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ build = "build.rs"
1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313

1414
[dependencies]
15+
16+
[dev-dependencies]
17+
bump2version = "0.1.4"
18+
crc32fast = "1.4.2"
19+
criterion = { version = "0.5.1", features = ["html_reports"] }
20+
21+
[[bench]]
22+
name = "benchmark"
23+
harness = false

README.md

+70-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# CRC32
22

3+
> Fastest CRC32 Rust Implementation
4+
35
Resurrecting the [`crc32`](https://crates.io/crates/crc32) crate from the ashes.
46

57
### Usage
@@ -46,4 +48,71 @@ fn main() {
4648

4749
// CRC-32: ebe6c6e6
4850
// CRC-32 (Little Endian): a29eb9bf
49-
```
51+
```
52+
53+
### Benchmark
54+
55+
Running `cargo bench` provides the following performance insights:
56+
57+
<details>
58+
<summary><code>cargo bench</code></summary>
59+
60+
```sh
61+
cargo bench
62+
63+
Compiling crc32-v2 v0.0.4 (/home/mahmoud/Desktop/TODO/crc32-v2)
64+
Finished `bench` profile [optimized] target(s) in 2.06s
65+
Running unittests src/lib.rs (target/release/deps/crc32_v2-3c56bd9cac40bc4d)
66+
67+
running 0 tests
68+
69+
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
70+
71+
Running benches/benchmark.rs (target/release/deps/benchmark-f87f05a33c0b9723)
72+
Benchmarking `crc32-v2` crc32_while_loop(0, b"hello") performance:: Warming up fBenchmarking `crc32-v2` crc32_while_loop(0, b"hello") performance:: Collecting 1`crc32-v2` crc32_while_loop(0, b"hello") performance:
73+
time: [6.6916 ns 6.7111 ns 6.7306 ns]
74+
change: [-6.2932% -5.1894% -3.8925%] (p = 0.00 < 0.05)
75+
Performance has improved.
76+
Found 14 outliers among 100 measurements (14.00%)
77+
7 (7.00%) low mild
78+
2 (2.00%) high mild
79+
5 (5.00%) high severe
80+
81+
Benchmarking `crc32-v2` crc32_for_loop(0, b"hello") performance:: Warming up forBenchmarking `crc32-v2` crc32_for_loop(0, b"hello") performance:: Collecting 100`crc32-v2` crc32_for_loop(0, b"hello") performance:
82+
time: [6.7552 ns 6.7719 ns 6.7884 ns]
83+
change: [-16.025% -12.793% -9.5779%] (p = 0.00 < 0.05)
84+
Performance has improved.
85+
Found 4 outliers among 100 measurements (4.00%)
86+
1 (1.00%) high mild
87+
3 (3.00%) high severe
88+
89+
Benchmarking `crc32fast` crc32fast::hash(b"hello") performance:: Warming up for Benchmarking `crc32fast` crc32fast::hash(b"hello") performance:: Collecting 100 `crc32fast` crc32fast::hash(b"hello") performance:
90+
time: [8.4118 ns 8.4320 ns 8.4522 ns]
91+
change: [-9.0567% -7.0970% -5.3253%] (p = 0.00 < 0.05)
92+
Performance has improved.
93+
Found 4 outliers among 100 measurements (4.00%)
94+
4 (4.00%) high severe
95+
96+
Benchmarking `crc32fast` crc32fast::Hasher::new().update(b"hello").finalize() peBenchmarking `crc32fast` crc32fast::Hasher::new().update(b"hello").finalize() peBenchmarking `crc32fast` crc32fast::Hasher::new().update(b"hello").finalize() peBenchmarking `crc32fast` crc32fast::Hasher::new().update(b"hello").finalize() pe`crc32fast` crc32fast::Hasher::new().update(b"hello").finalize() performance:
97+
time: [21.334 ns 21.379 ns 21.419 ns]
98+
change: [-6.8627% -5.2041% -3.7228%] (p = 0.00 < 0.05)
99+
Performance has improved.
100+
Found 5 outliers among 100 measurements (5.00%)
101+
2 (2.00%) high mild
102+
3 (3.00%) high severe
103+
```
104+
105+
</details>
106+
107+
Below is a summarized table of results, highlighting the execution times for each method:
108+
109+
| **Method** | **Mean Time (ns)** | **Outliers (%)** | **Description** |
110+
|-----------------------------------------------|---------------------|-------------------|------------------------------------------------|
111+
| `crc32_v2_while_loop(0, b"hello")` | 6.71 | 14.00% | CRC32-V2 built-in function using a while loop, optimized. |
112+
| `crc32_v2_for_loop(0, b"hello")` | 6.77 | 4.00% | Custom CRC32-V2 using a for loop, optimized. |
113+
| `crc32fast::hash(b"hello")` | 8.43 | 4.00% | crc32fast built-in hashing method. |
114+
| `crc32fast::Hasher::new().update(b"hello").finalize()` | 21.38 | 5.00% | Hasher object approach, slower initialization.|
115+
116+
1. **Fastest Method**: The built-in `crc32_v2_while_loop` implementation shows the best performance, slightly outperforming the `crc32_for_loop`.
117+
1. **crc32fast Performance**: The `crc32fast::hash` method is slightly slower than the `crc32-v2` implementation.
118+
1. **Hasher Object Method**: The `crc32fast::Hasher` object approach is significantly slower for some reason.

benches/benchmark.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crc32_v2::crc32tables::CRC_TABLE;
2+
use crc32fast::Hasher;
3+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
4+
5+
#[inline]
6+
pub fn crc32_while_loop(start_crc: u32, buf: &[u8]) -> u32 {
7+
// Initialize variables
8+
let len = buf.len();
9+
let mut crc = start_crc;
10+
let mut bufpos: usize = 0;
11+
let mut remaining_bytes = len;
12+
13+
// XOR with 0xffffffff as specified in CRC32 algorithm
14+
crc = crc ^ 0xffffffff;
15+
16+
// Reference to the first CRC table for faster access
17+
let t0 = &CRC_TABLE[0];
18+
19+
// Process each byte in the buffer
20+
while remaining_bytes > 0 {
21+
let b = buf[bufpos];
22+
let b32 = b as u32;
23+
let b_index = (crc ^ b32) & 0xff;
24+
let t = t0[b_index as usize];
25+
crc = t ^ (crc >> 8);
26+
bufpos += 1;
27+
remaining_bytes -= 1;
28+
}
29+
30+
// XOR again with 0xffffffff as specified in CRC32 algorithm
31+
crc ^ 0xffffffff
32+
}
33+
34+
#[inline]
35+
pub fn crc32_for_loop(start_crc: u32, buf: &[u8]) -> u32 {
36+
// XOR with 0xffffffff as specified in the CRC32 algorithm
37+
let mut crc = start_crc ^ 0xffffffff;
38+
39+
// Reference to the first CRC table for faster access
40+
let t0 = &CRC_TABLE[0];
41+
42+
// Process each byte in the buffer
43+
for &byte in buf {
44+
let b_index = (crc ^ (byte as u32)) & 0xff;
45+
crc = t0[b_index as usize] ^ (crc >> 8);
46+
}
47+
48+
crc ^ 0xffffffff
49+
}
50+
51+
fn criterion_benchmark(c: &mut Criterion) {
52+
c.bench_function(
53+
"`crc32-v2` crc32_while_loop(0, b\"hello\") performance:",
54+
|b| b.iter(|| crc32_while_loop(black_box(0), black_box(b"hello"))),
55+
);
56+
c.bench_function(
57+
"`crc32-v2` crc32_for_loop(0, b\"hello\") performance:",
58+
|b| b.iter(|| crc32_for_loop(black_box(0), black_box(b"hello"))),
59+
);
60+
c.bench_function(
61+
"`crc32fast` crc32fast::hash(b\"hello\") performance:",
62+
|b| b.iter(|| crc32fast::hash(black_box(b"hello"))),
63+
);
64+
c.bench_function(
65+
"`crc32fast` crc32fast::Hasher::new().update(b\"hello\").finalize() performance:",
66+
|b| {
67+
b.iter(|| {
68+
let mut hasher = black_box(Hasher::new());
69+
hasher.update(black_box(b"foo bar baz"));
70+
let _checksum = hasher.finalize();
71+
})
72+
},
73+
);
74+
}
75+
76+
criterion_group!(benches, criterion_benchmark);
77+
criterion_main!(benches);

build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn main() {
1515

1616
let outpath = Path::new(outfile_name);
1717

18-
let outfile = File::create(&outpath);
18+
let outfile = File::create(outpath);
1919

2020
match outfile {
2121
Ok(file) => {

src/byfour.rs

+13-17
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,9 @@ pub fn dolit4(c: &mut u32, buf4: &[u32], buf4pos: &mut usize) {
4646
/// assert_eq!(crc, 0);
4747
/// ```
4848
pub fn dolit32(c: &mut u32, buf4: &[u32], buf4pos: &mut usize) {
49-
dolit4(c, buf4, buf4pos);
50-
dolit4(c, buf4, buf4pos);
51-
dolit4(c, buf4, buf4pos);
52-
dolit4(c, buf4, buf4pos);
53-
dolit4(c, buf4, buf4pos);
54-
dolit4(c, buf4, buf4pos);
55-
dolit4(c, buf4, buf4pos);
56-
dolit4(c, buf4, buf4pos);
49+
for _ in 0..8 {
50+
dolit4(c, buf4, buf4pos);
51+
}
5752
}
5853

5954
/// This function converts a slice of u8 into a slice of u32.
@@ -75,11 +70,13 @@ pub fn dolit32(c: &mut u32, buf4: &[u32], buf4pos: &mut usize) {
7570
/// let u32_slice = slice_u8_as_u32(&bytes);
7671
/// assert_eq!(u32_slice, &[50462976u32, 117835012u32]);
7772
/// ```
78-
pub fn slice_u8_as_u32(s8: &[u8]) -> &[u32] {
79-
let len_u32 = s8.len() / 4;
80-
let ptr: *const u32 = s8.as_ptr() as *const u32;
81-
82-
unsafe { std::slice::from_raw_parts(ptr, len_u32) }
73+
pub fn slice_u8_as_u32(s8: &[u8]) -> Vec<u32> {
74+
s8.chunks_exact(4)
75+
.map(|chunk| {
76+
// Convert 4-bytes chunks into a u32 value
77+
u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])
78+
})
79+
.collect()
8380
}
8481

8582
/// This function calculates the CRC32 checksum of a byte buffer in little-endian format.
@@ -118,11 +115,11 @@ pub fn crc32_little(crc: u32, buf: &[u8]) -> u32 {
118115
let buf4 = slice_u8_as_u32(&buf[bufpos..]);
119116
let mut buf4pos: usize = 0;
120117
while len >= 32 {
121-
dolit32(&mut c, buf4, &mut buf4pos);
118+
dolit32(&mut c, &buf4, &mut buf4pos);
122119
len -= 32;
123120
}
124121
while len >= 4 {
125-
dolit4(&mut c, buf4, &mut buf4pos);
122+
dolit4(&mut c, &buf4, &mut buf4pos);
126123
len -= 4;
127124
}
128125

@@ -142,6 +139,5 @@ pub fn crc32_little(crc: u32, buf: &[u8]) -> u32 {
142139
}
143140
}
144141
}
145-
c = !c;
146-
return c;
142+
!c
147143
}

src/crc32gen.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ pub fn write_tables(crc_table: &CrcTable) -> String {
9191
write_table(&mut s, &tt[0]);
9292
// # ifdef BYFOUR
9393
s.push_str("// #ifdef BYFOUR\n");
94-
for k in 1..8 {
94+
for item in tt.iter().take(8).skip(1) {
9595
s.push_str(" ],\n [\n");
96-
write_table(&mut s, &tt[k]);
96+
write_table(&mut s, item);
9797
}
9898
s.push_str("// #endif\n");
9999
// # endif /* BYFOUR */
@@ -103,11 +103,11 @@ pub fn write_tables(crc_table: &CrcTable) -> String {
103103
}
104104

105105
fn write_table(s: &mut String, table: &[u32; 0x100]) {
106-
for n in 0..0x100 {
106+
for (n, item) in table.iter().enumerate().take(0x100) {
107107
let line = format!(
108108
"{}0x{:08x}{}",
109109
if n % 5 != 0 { "" } else { " " },
110-
table[n],
110+
item,
111111
if n == 255 {
112112
"\n"
113113
} else if n % 5 == 4 {

0 commit comments

Comments
 (0)