Skip to content

Commit d184ddd

Browse files
authored
Add bench test (#20)
1 parent 75b83aa commit d184ddd

File tree

8 files changed

+144
-37
lines changed

8 files changed

+144
-37
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,9 @@ bitstream-io = "1.2.0"
2121
thiserror = "1.0.30"
2222

2323
[dev-dependencies]
24-
test-case = "2.0.0"
2524
fenwick-model = { path = "./fenwick-model" }
25+
criterion = "0.3.5"
26+
27+
[[bench]]
28+
name = "sherlock"
29+
harness = false

benches/common/mod.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use arithmetic_coding::{Decoder, Encoder, Model};
2+
use bitstream_io::{BigEndian, BitReader, BitWrite, BitWriter};
3+
4+
pub fn round_trip<M>(model: M, input: &[M::Symbol])
5+
where
6+
M: Model + Clone,
7+
M::Symbol: Copy + std::fmt::Debug + PartialEq,
8+
{
9+
let buffer = encode(model.clone(), input.iter().copied());
10+
11+
let mut output = Vec::with_capacity(input.len());
12+
for symbol in decode(model, &buffer) {
13+
output.push(symbol);
14+
}
15+
16+
assert_eq!(input, output.as_slice());
17+
}
18+
19+
pub fn encode<M, I>(model: M, input: I) -> Vec<u8>
20+
where
21+
M: Model,
22+
I: IntoIterator<Item = M::Symbol>,
23+
{
24+
let mut bitwriter = BitWriter::endian(Vec::new(), BigEndian);
25+
let mut encoder = Encoder::<M>::new(model);
26+
27+
encoder.encode_all(input, &mut bitwriter).unwrap();
28+
bitwriter.byte_align().unwrap();
29+
30+
bitwriter.into_writer()
31+
}
32+
33+
pub fn decode<M>(model: M, buffer: &[u8]) -> Vec<M::Symbol>
34+
where
35+
M: Model,
36+
{
37+
let bitreader = BitReader::endian(buffer, BigEndian);
38+
let mut decoder = Decoder::new(model, bitreader).unwrap();
39+
let mut output = Vec::new();
40+
41+
while let Some(symbol) = decoder.decode_symbol().unwrap() {
42+
output.push(symbol);
43+
}
44+
output
45+
}

benches/sherlock.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use std::{fs::File, io::Read, ops::Range};
2+
3+
use arithmetic_coding::Model;
4+
use fenwick_model::{simple::FenwickModel, ValueError};
5+
6+
mod common;
7+
8+
#[derive(Debug, Clone)]
9+
pub struct StringModel {
10+
fenwick_model: FenwickModel,
11+
}
12+
13+
impl StringModel {
14+
#[must_use]
15+
pub fn new(symbols: usize) -> Self {
16+
let fenwick_model = FenwickModel::with_symbols(symbols, 1 << 20);
17+
Self { fenwick_model }
18+
}
19+
}
20+
21+
#[derive(Debug, thiserror::Error)]
22+
#[error("invalid character: {0}")]
23+
pub struct Error(char);
24+
25+
impl Model for StringModel {
26+
type B = u64;
27+
type Symbol = u8;
28+
type ValueError = ValueError;
29+
30+
fn probability(
31+
&self,
32+
symbol: Option<&Self::Symbol>,
33+
) -> Result<Range<Self::B>, Self::ValueError> {
34+
let fenwick_symbol = symbol.map(|c| *c as usize);
35+
self.fenwick_model.probability(fenwick_symbol.as_ref())
36+
}
37+
38+
fn symbol(&self, value: Self::B) -> Option<Self::Symbol> {
39+
self.fenwick_model.symbol(value).map(|x| x as u8)
40+
}
41+
42+
fn max_denominator(&self) -> Self::B {
43+
self.fenwick_model.max_denominator()
44+
}
45+
46+
fn denominator(&self) -> Self::B {
47+
self.fenwick_model.denominator()
48+
}
49+
}
50+
51+
fn round_trip(input: &[u8]) {
52+
let model = StringModel::new(256);
53+
54+
common::round_trip(model, input);
55+
}
56+
57+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
58+
59+
pub fn criterion_benchmark(c: &mut Criterion) {
60+
let mut input_string = String::new();
61+
File::open("./resources/sherlock.txt")
62+
.unwrap()
63+
.read_to_string(&mut input_string)
64+
.unwrap();
65+
66+
let truncated: String = input_string.chars().take(3428).collect();
67+
let input = truncated.as_bytes();
68+
69+
c.bench_function("round trip", |b| b.iter(|| round_trip(black_box(input))));
70+
}
71+
72+
criterion_group!(benches, criterion_benchmark);
73+
criterion_main!(benches);

examples/fenwick_adaptive.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use arithmetic_coding::Model;
44

55
mod common;
66

7-
use fenwick_model::simple::{FenwickModel, ValueError};
7+
use fenwick_model::{simple::FenwickModel, ValueError};
88

99
const ALPHABET: &str =
1010
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,\n-':()[]#*;\"!?*&é/àâè%@$";
@@ -18,7 +18,7 @@ pub struct StringModel {
1818
impl StringModel {
1919
#[must_use]
2020
pub fn new(alphabet: Vec<char>) -> Self {
21-
let fenwick_model = FenwickModel::with_symbols(alphabet.len());
21+
let fenwick_model = FenwickModel::with_symbols(alphabet.len(), 1 << 20);
2222
Self {
2323
alphabet,
2424
fenwick_model,

examples/fenwick_context_switching.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use arithmetic_coding::Model;
44

55
mod common;
66

7-
use fenwick_model::context_switching::{FenwickModel, ValueError};
7+
use fenwick_model::{context_switching::FenwickModel, ValueError};
88

99
const ALPHABET: &str =
1010
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,\n-':()[]#*;\"!?*&é/àâè%@$";
@@ -18,7 +18,7 @@ pub struct StringModel {
1818
impl StringModel {
1919
#[must_use]
2020
pub fn new(alphabet: Vec<char>) -> Self {
21-
let fenwick_model = FenwickModel::with_symbols(alphabet.len());
21+
let fenwick_model = FenwickModel::with_symbols(alphabet.len(), 1 << 17);
2222
Self {
2323
alphabet,
2424
fenwick_model,

fenwick-model/src/context_switching.rs

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,29 @@
1-
#![allow(missing_docs, unused)]
21
//! Fenwick tree based context-switching model
32
43
use arithmetic_coding_core::Model;
54

65
use super::Weights;
6+
use crate::ValueError;
77

88
#[derive(Debug, Clone)]
99
pub struct FenwickModel {
1010
contexts: Vec<Weights>,
11-
previous_context: usize,
1211
current_context: usize,
13-
denominator: u64,
1412
max_denominator: u64,
1513
}
1614

1715
impl FenwickModel {
1816
#[must_use]
19-
pub fn with_symbols(symbols: usize) -> Self {
17+
pub fn with_symbols(symbols: usize, max_denominator: u64) -> Self {
2018
let mut contexts = Vec::with_capacity(symbols + 1);
21-
let mut denominator = 0;
22-
let max_denominator = 1 << 17;
2319

2420
for _ in 0..=symbols {
25-
let weight = Weights::new(symbols);
26-
denominator = denominator.max(weight.total());
2721
contexts.push(Weights::new(symbols));
2822
}
2923

3024
Self {
3125
contexts,
32-
previous_context: 1,
3326
current_context: 1,
34-
denominator,
3527
max_denominator,
3628
}
3729
}
@@ -45,42 +37,34 @@ impl FenwickModel {
4537
}
4638
}
4739

48-
#[derive(Debug, thiserror::Error)]
49-
#[error("invalid symbol received: {0}")]
50-
pub struct ValueError(usize);
51-
5240
impl Model for FenwickModel {
5341
type B = u64;
5442
type Symbol = usize;
5543
type ValueError = ValueError;
5644

57-
fn probability(
58-
&self,
59-
symbol: Option<&Self::Symbol>,
60-
) -> Result<std::ops::Range<Self::B>, Self::ValueError> {
45+
fn probability(&self, symbol: Option<&usize>) -> Result<std::ops::Range<u64>, ValueError> {
6146
Ok(self.context().range(symbol.copied()))
6247
}
6348

64-
fn max_denominator(&self) -> Self::B {
65-
self.max_denominator
49+
fn denominator(&self) -> u64 {
50+
self.context().total
6651
}
6752

68-
fn symbol(&self, value: Self::B) -> Option<Self::Symbol> {
69-
self.context().symbol(value)
53+
fn max_denominator(&self) -> u64 {
54+
self.max_denominator
7055
}
7156

72-
fn denominator(&self) -> Self::B {
73-
self.context().total
57+
fn symbol(&self, value: u64) -> Option<usize> {
58+
self.context().symbol(value)
7459
}
7560

76-
fn update(&mut self, symbol: Option<&Self::Symbol>) {
61+
fn update(&mut self, symbol: Option<&usize>) {
7762
debug_assert!(
7863
self.denominator() < self.max_denominator,
7964
"hit max denominator!"
8065
);
8166
if self.denominator() < self.max_denominator {
8267
self.context_mut().update(symbol.copied(), 1);
83-
self.denominator = self.denominator.max(self.context().total());
8468
}
8569
self.current_context = symbol.map(|x| x + 1).unwrap_or_default();
8670
}

fenwick-model/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ impl Weights {
7575
self.total
7676
}
7777
}
78+
79+
#[derive(Debug, thiserror::Error)]
80+
#[error("invalid symbol received: {0}")]
81+
pub struct ValueError(pub usize);

fenwick-model/src/simple.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use arithmetic_coding_core::Model;
55

66
use super::Weights;
7+
use crate::ValueError;
78

89
#[derive(Debug, Clone)]
910
pub struct FenwickModel {
@@ -13,20 +14,16 @@ pub struct FenwickModel {
1314

1415
impl FenwickModel {
1516
#[must_use]
16-
pub fn with_symbols(symbols: usize) -> Self {
17+
pub fn with_symbols(symbols: usize, max_denominator: u64) -> Self {
1718
let weights = Weights::new(symbols);
1819

1920
Self {
2021
weights,
21-
max_denominator: 1 << 17,
22+
max_denominator,
2223
}
2324
}
2425
}
2526

26-
#[derive(Debug, thiserror::Error)]
27-
#[error("invalid symbol received: {0}")]
28-
pub struct ValueError(pub usize);
29-
3027
impl Model for FenwickModel {
3128
type B = u64;
3229
type Symbol = usize;

0 commit comments

Comments
 (0)