Skip to content

Commit

Permalink
alloc: Reduce unnecessary Vec allocations and indirections
Browse files Browse the repository at this point in the history
* Changed literal_probs array from a Vec<Vec<u16>> to a Vec2D backed by a contiguous Vec<u16>.
* BitTrees in LenDecoder and DecoderState are now stored inline
  • Loading branch information
chyyran committed Aug 6, 2022
1 parent 6e1f0d7 commit c4a996a
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 10 deletions.
18 changes: 12 additions & 6 deletions src/decode/lzma.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::decode::lzbuffer::{LzBuffer, LzCircularBuffer};
use crate::decode::rangecoder;
use crate::decode::rangecoder::RangeDecoder;
use crate::decode::vec2d::Vec2D;
use crate::decompress::Options;
use crate::decompress::UnpackedSize;
use crate::error;
Expand Down Expand Up @@ -167,8 +168,8 @@ pub(crate) struct DecoderState {
partial_input_buf: std::io::Cursor<[u8; MAX_REQUIRED_INPUT]>,
pub(crate) lzma_props: LzmaProperties,
unpacked_size: Option<u64>,
literal_probs: Vec<Vec<u16>>,
pos_slot_decoder: Vec<rangecoder::BitTree>,
literal_probs: Vec2D<u16>,
pos_slot_decoder: [rangecoder::BitTree; 4],
align_decoder: rangecoder::BitTree,
pos_decoders: [u16; 115],
is_match: [u16; 192], // true = LZ, false = literal
Expand All @@ -190,8 +191,13 @@ impl DecoderState {
partial_input_buf: std::io::Cursor::new([0; MAX_REQUIRED_INPUT]),
lzma_props,
unpacked_size,
literal_probs: vec![vec![0x400; 0x300]; 1 << (lzma_props.lc + lzma_props.lp)],
pos_slot_decoder: vec![rangecoder::BitTree::new(6); 4],
literal_probs: Vec2D::init(1 << (lzma_props.lc + lzma_props.lp), 0x300, 0x400),
pos_slot_decoder: [
rangecoder::BitTree::new(6),
rangecoder::BitTree::new(6),
rangecoder::BitTree::new(6),
rangecoder::BitTree::new(6),
],
align_decoder: rangecoder::BitTree::new(4),
pos_decoders: [0x400; 115],
is_match: [0x400; 192],
Expand All @@ -211,10 +217,10 @@ impl DecoderState {
new_props.validate();
if self.lzma_props.lc + self.lzma_props.lp == new_props.lc + new_props.lp {
// We can reset here by filling the existing buffer with 0x400.
self.literal_probs.iter_mut().for_each(|v| v.fill(0x400))
self.literal_probs.fill(0x400);
} else {
// We need to reallocate because of the new size of `lc+lp`.
self.literal_probs = vec![vec![0x400; 0x300]; 1 << (new_props.lc + new_props.lp)];
self.literal_probs = Vec2D::init(1 << (new_props.lc + new_props.lp), 0x300, 0x400);
}

self.lzma_props = new_props;
Expand Down
1 change: 1 addition & 0 deletions src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod lzma2;
pub mod options;
pub mod rangecoder;
pub mod util;
pub mod vec2d;
pub mod xz;

#[cfg(feature = "stream")]
Expand Down
42 changes: 38 additions & 4 deletions src/decode/rangecoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ impl BitTree {
pub struct LenDecoder {
choice: u16,
choice2: u16,
low_coder: Vec<BitTree>,
mid_coder: Vec<BitTree>,
low_coder: [BitTree; 16],
mid_coder: [BitTree; 16],
high_coder: BitTree,
}

Expand All @@ -200,8 +200,42 @@ impl LenDecoder {
LenDecoder {
choice: 0x400,
choice2: 0x400,
low_coder: vec![BitTree::new(3); 16],
mid_coder: vec![BitTree::new(3); 16],
low_coder: [
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
],
mid_coder: [
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
BitTree::new(3),
],
high_coder: BitTree::new(8),
}
}
Expand Down
74 changes: 74 additions & 0 deletions src/decode/vec2d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use std::ops::{Index, IndexMut};

/// A 2 dimensional matrix in row-major order backed by a contiguous `Vec`
#[derive(Debug)]
pub struct Vec2D<T> {
data: Vec<T>,
cols: usize,
rows: usize,
}

impl<T> Vec2D<T> {
/// Initialize a grid of size `rows * cols` with the given data element.
pub fn init(rows: usize, cols: usize, data: T) -> Vec2D<T>
where
T: Clone,
{
Vec2D {
data: vec![data; rows * cols],
cols,
rows,
}
}

/// Fills the grid with elements by cloning `value`.
pub fn fill(&mut self, value: T)
where
T: Clone,
{
self.data.fill(value)
}
}

impl<T> Index<usize> for Vec2D<T> {
type Output = [T];

fn index(&self, idx: usize) -> &Self::Output {
if idx < self.rows {
let start_idx = idx * self.cols;
&self.data[start_idx..start_idx + self.cols]
} else {
panic!("row index {:?} out of bounds.", idx);
}
}
}

impl<T> IndexMut<usize> for Vec2D<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
&mut self.data[(idx * self.cols)..]
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn vec2d_fill() {
let mut vec2d = Vec2D::init(3, 3, 0);
vec2d.fill(7);
assert_eq!(vec2d[0], [7, 7, 7]);
assert_eq!(vec2d[1], [7, 7, 7]);
}

#[test]
fn vec2d_index() {
let vec2d = Vec2D {
data: vec![0, 1, 2, 3, 4, 5],
rows: 3,
cols: 3,
};
assert_eq!(vec2d[0], [0, 1, 2]);
assert_eq!(vec2d[1], [3, 4, 5]);
}
}

0 comments on commit c4a996a

Please sign in to comment.