Skip to content

Commit 95f1b1c

Browse files
authored
Merge pull request #61 from lilith/fixed-array-table
perf: fixed-size decode table — zero bounds checks, +3-9% throughput
2 parents bb5cd04 + 75dc30e commit 95f1b1c

1 file changed

Lines changed: 64 additions & 58 deletions

File tree

src/decode.rs

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ trait Stateful {
8282
fn reset(&mut self);
8383
}
8484

85-
#[derive(Clone)]
85+
#[derive(Clone, Copy, Default)]
8686
struct Link {
8787
prev: Code,
8888
byte: u8,
@@ -178,9 +178,16 @@ struct Buffer {
178178
write_mark: usize,
179179
}
180180

181+
/// Mask for indexing into fixed-size arrays. Since MAX_ENTRIES = 4096 = 2^12,
182+
/// `idx & MASK` is guaranteed < MAX_ENTRIES. LLVM can prove this for
183+
/// `[T; MAX_ENTRIES]` arrays, eliminating bounds checks. Corrupt `prev`
184+
/// values wrap to a valid index instead of panicking.
185+
const MASK: usize = MAX_ENTRIES - 1;
186+
181187
struct Table {
182-
inner: Vec<Link>,
183-
depths: Vec<u16>,
188+
inner: Box<[Link; MAX_ENTRIES]>,
189+
depths: Box<[u16; MAX_ENTRIES]>,
190+
len: usize,
184191
}
185192

186193
/// Describes the static parameters for creating a decoder.
@@ -908,7 +915,7 @@ impl<C: CodeBuffer, CgC: CodegenConstants> Stateful for DecodeState<C, CgC> {
908915

909916
debug_assert!(
910917
// When the table is full, we have a max code above the size switch.
911-
self.table.inner.len() >= MAX_ENTRIES - usize::from(self.is_tiff)
918+
self.table.len >= MAX_ENTRIES - usize::from(self.is_tiff)
912919
// When the code size is 2 we have a bit code: (0, 1, CLS, EOF). Then the
913920
// computed next_code is 4 which already exceeds the bit width from the start.
914921
// Then we will immediately switch code size after this code.
@@ -966,7 +973,7 @@ impl<C: CodeBuffer, CgC: CodegenConstants> Stateful for DecodeState<C, CgC> {
966973
}
967974

968975
// Read the code length and check that we can decode directly into the out slice.
969-
let len = self.table.depths[usize::from(read_code)];
976+
let len = self.table.depths[usize::from(read_code) & MASK];
970977

971978
if out.len() < usize::from(len) {
972979
break;
@@ -1031,9 +1038,9 @@ impl<C: CodeBuffer, CgC: CodegenConstants> Stateful for DecodeState<C, CgC> {
10311038
}
10321039

10331040
let required_len = if new_code == self.next_code {
1034-
self.table.depths[usize::from(deriv.code)] + 1
1041+
self.table.depths[usize::from(deriv.code) & MASK] + 1
10351042
} else {
1036-
self.table.depths[usize::from(new_code)]
1043+
self.table.depths[usize::from(new_code) & MASK]
10371044
};
10381045

10391046
// We need the decoded data of the new code if it is the `next_code`. This is the
@@ -1324,7 +1331,7 @@ impl CodeBuffer for LsbBuffer {
13241331
new_bits as u8
13251332
}
13261333
};
1327-
self.bit_buffer |= u64::from_be_bytes(buffer).swap_bytes() << self.bits;
1334+
self.bit_buffer |= u64::from_le_bytes(buffer) << self.bits;
13281335
self.bits += new_bits;
13291336
}
13301337

@@ -1397,7 +1404,7 @@ impl Buffer {
13971404
fn fill_reconstruct(&mut self, table: &Table, code: Code) -> u8 {
13981405
self.write_mark = 0;
13991406
self.read_mark = 0;
1400-
let depth = table.depths[usize::from(code)];
1407+
let depth = table.depths[usize::from(code) & MASK];
14011408
let mut memory = core::mem::replace(&mut self.bytes, Box::default());
14021409

14031410
let out = &mut memory[..usize::from(depth)];
@@ -1417,89 +1424,88 @@ impl Buffer {
14171424
}
14181425
}
14191426

1427+
fn boxed_arr<T: Clone + Default, const N: usize>() -> Box<[T; N]> {
1428+
use core::convert::TryInto;
1429+
vec![T::default(); N]
1430+
.into_boxed_slice()
1431+
.try_into()
1432+
.ok()
1433+
.unwrap()
1434+
}
1435+
14201436
impl Table {
14211437
fn new() -> Self {
14221438
Table {
1423-
inner: Vec::with_capacity(MAX_ENTRIES),
1424-
depths: Vec::with_capacity(MAX_ENTRIES),
1439+
inner: boxed_arr(),
1440+
depths: boxed_arr(),
1441+
len: 0,
14251442
}
14261443
}
14271444

14281445
fn clear(&mut self, min_size: u8) {
1429-
let static_count = usize::from(1u16 << u16::from(min_size)) + 2;
1430-
self.inner.truncate(static_count);
1431-
self.depths.truncate(static_count);
1446+
self.len = usize::from(1u16 << u16::from(min_size)) + 2;
14321447
}
14331448

14341449
fn init(&mut self, min_size: u8) {
1435-
self.inner.clear();
1436-
self.depths.clear();
1450+
self.len = 0;
14371451
for i in 0..(1u16 << u16::from(min_size)) {
1438-
self.inner.push(Link::base(i as u8));
1439-
self.depths.push(1);
1452+
let idx = self.len & MASK;
1453+
self.inner[idx] = Link::base(i as u8);
1454+
self.depths[idx] = 1;
1455+
self.len += 1;
1456+
}
1457+
// Clear code + End code: skip writing when the masked index would
1458+
// alias an alphabet entry (happens at min_size=12 where clear=4096
1459+
// wraps to index 0).
1460+
for _ in 0..2 {
1461+
if self.len < MAX_ENTRIES {
1462+
let idx = self.len & MASK;
1463+
self.inner[idx] = Link::base(0);
1464+
self.depths[idx] = 0;
1465+
}
1466+
self.len += 1;
14401467
}
1441-
// Clear code.
1442-
self.inner.push(Link::base(0));
1443-
self.depths.push(0);
1444-
// End code.
1445-
self.inner.push(Link::base(0));
1446-
self.depths.push(0);
14471468
}
14481469

14491470
fn at(&self, code: Code) -> &Link {
1450-
&self.inner[usize::from(code)]
1471+
&self.inner[usize::from(code) & MASK]
14511472
}
14521473

14531474
fn is_empty(&self) -> bool {
1454-
self.inner.is_empty()
1475+
self.len == 0
14551476
}
14561477

14571478
fn is_full(&self) -> bool {
1458-
self.inner.len() >= MAX_ENTRIES
1479+
self.len >= MAX_ENTRIES
14591480
}
14601481

14611482
fn derive(&mut self, from: &DerivationBase, byte: u8) {
1462-
let link = from.derive(byte);
1463-
let depth = self.depths[usize::from(from.code)] + 1;
1464-
self.inner.push(link);
1465-
self.depths.push(depth);
1483+
let idx = self.len & MASK;
1484+
let depth = self.depths[usize::from(from.code) & MASK] + 1;
1485+
self.inner[idx] = from.derive(byte);
1486+
self.depths[idx] = depth;
1487+
self.len += 1;
14661488
}
14671489

1468-
// Derive multiple codes in a row, where each base is guaranteed to already exist.
14691490
fn derive_burst(&mut self, from: &mut DerivationBase, burst: &[Code], first: &[u8]) {
1470-
let mut depth_of = from.code;
1471-
// Note that false data dependency we want to get rid of!
1472-
// TODO: this pushes into a Vec, maybe we can make this cleaner.
1473-
for &code in burst {
1474-
let depth = self.depths[usize::from(depth_of)] + 1;
1475-
self.depths.push(depth);
1476-
depth_of = code;
1477-
}
1478-
1479-
// Llvm tends to be flaky with code layout for the case of requiring an allocation. It's
1480-
// not clear if that can occur in practice but it relies on iterator size hint..
1481-
let extensions = burst.iter().zip(first);
1482-
self.inner.extend(extensions.map(|(&code, &first)| {
1483-
let link = from.derive(first);
1491+
for (&code, &first_byte) in burst.iter().zip(first.iter()) {
1492+
self.derive(from, first_byte);
14841493
from.code = code;
1485-
from.first = first;
1486-
link
1487-
}));
1494+
from.first = first_byte;
1495+
}
14881496
}
14891497

14901498
fn reconstruct(&self, code: Code, out: &mut [u8]) -> u8 {
14911499
let mut code_iter = code;
1492-
let table = &self.inner[..=usize::from(code)];
1493-
let first = table[usize::from(code)].first;
1500+
let first = self.inner[usize::from(code) & MASK].first;
14941501

1495-
let len = code_iter;
1502+
// The `& MASK` ensures any prev value (even from corrupt data) maps
1503+
// to a valid array index. This replaces the previous `min(len, prev)`
1504+
// clamp with equivalent safety: no panic, bounded loop, wrong output
1505+
// on corrupt data.
14961506
for ch in out.iter_mut().rev() {
1497-
//(code, cha) = self.table[k as usize];
1498-
// Note: This could possibly be replaced with an unchecked array access if
1499-
// - value is asserted to be < self.next_code() in push
1500-
// - min_size is asserted to be < MAX_CODESIZE
1501-
let entry = &table[usize::from(code_iter)];
1502-
code_iter = core::cmp::min(len, entry.prev);
1507+
let entry = &self.inner[usize::from(code_iter) & MASK];
1508+
code_iter = entry.prev;
15031509
*ch = entry.byte;
15041510
}
15051511

0 commit comments

Comments
 (0)