Skip to content

Commit 160bcff

Browse files
authored
uses LruCache instead of InversionTree for caching data decode matrices (#104)
* adds benchmark for reconstruct * uses LruCache instead of InversionTree for caching data decode matrices Current implementation of LRU eviction policy on InversionTree is wrong and inefficient. The commit removes InversionTree and instead uses LruCache to cache data decode matrices.
1 parent eb1f66f commit 160bcff

File tree

5 files changed

+151
-542
lines changed

5 files changed

+151
-542
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ coveralls = { repository = "darrenldl/reed-solomon-erasure" }
3838
libc = { version = "0.2", optional = true }
3939
# `log2()` impl for `no_std`
4040
libm = "0.2.1"
41+
lru = "0.7.8"
4142
# Efficient `Mutex` implementation for `std` environment
4243
parking_lot = { version = "0.11.2", optional = true }
4344
smallvec = "1.2"
@@ -50,3 +51,6 @@ quickcheck = "0.9"
5051

5152
[build-dependencies]
5253
cc = { version = "1.0", optional = true }
54+
55+
[[bench]]
56+
name = "reconstruct"

benches/reconstruct.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#![feature(test)]
2+
3+
extern crate test;
4+
5+
use {
6+
rand::{prelude::*, Rng},
7+
reed_solomon_erasure::galois_8::Field,
8+
test::Bencher,
9+
};
10+
11+
type ReedSolomon = reed_solomon_erasure::ReedSolomon<Field>;
12+
13+
const SHARD_SIZE: usize = 1024;
14+
15+
fn run_reconstruct_bench(bencher: &mut Bencher, num_data_shards: usize, num_parity_shards: usize) {
16+
let mut rng = rand::thread_rng();
17+
let mut shards = vec![vec![0u8; SHARD_SIZE]; num_data_shards + num_parity_shards];
18+
for shard in &mut shards[..num_data_shards] {
19+
rng.fill(&mut shard[..]);
20+
}
21+
let reed_solomon = ReedSolomon::new(num_data_shards, num_parity_shards).unwrap();
22+
reed_solomon.encode(&mut shards[..]).unwrap();
23+
let shards: Vec<_> = shards.into_iter().map(Some).collect();
24+
25+
bencher.iter(|| {
26+
let mut shards = shards.clone();
27+
for _ in 0..num_parity_shards {
28+
*shards.choose_mut(&mut rng).unwrap() = None;
29+
}
30+
reed_solomon.reconstruct(&mut shards[..]).unwrap();
31+
assert!(shards.iter().all(Option::is_some));
32+
});
33+
}
34+
35+
#[bench]
36+
fn bench_reconstruct_2_2(bencher: &mut Bencher) {
37+
run_reconstruct_bench(bencher, 2, 2)
38+
}
39+
40+
#[bench]
41+
fn bench_reconstruct_4_2(bencher: &mut Bencher) {
42+
run_reconstruct_bench(bencher, 4, 2)
43+
}
44+
45+
#[bench]
46+
fn bench_reconstruct_4_4(bencher: &mut Bencher) {
47+
run_reconstruct_bench(bencher, 4, 4)
48+
}
49+
50+
#[bench]
51+
fn bench_reconstruct_8_2(bencher: &mut Bencher) {
52+
run_reconstruct_bench(bencher, 8, 2)
53+
}
54+
55+
#[bench]
56+
fn bench_reconstruct_8_4(bencher: &mut Bencher) {
57+
run_reconstruct_bench(bencher, 8, 4)
58+
}
59+
60+
#[bench]
61+
fn bench_reconstruct_8_8(bencher: &mut Bencher) {
62+
run_reconstruct_bench(bencher, 8, 8)
63+
}
64+
65+
#[bench]
66+
fn bench_reconstruct_16_2(bencher: &mut Bencher) {
67+
run_reconstruct_bench(bencher, 16, 2)
68+
}
69+
70+
#[bench]
71+
fn bench_reconstruct_16_4(bencher: &mut Bencher) {
72+
run_reconstruct_bench(bencher, 16, 4)
73+
}
74+
75+
#[bench]
76+
fn bench_reconstruct_16_8(bencher: &mut Bencher) {
77+
run_reconstruct_bench(bencher, 16, 8)
78+
}
79+
80+
#[bench]
81+
fn bench_reconstruct_16_16(bencher: &mut Bencher) {
82+
run_reconstruct_bench(bencher, 16, 16)
83+
}
84+
85+
#[bench]
86+
fn bench_reconstruct_32_2(bencher: &mut Bencher) {
87+
run_reconstruct_bench(bencher, 32, 2)
88+
}
89+
90+
#[bench]
91+
fn bench_reconstruct_32_4(bencher: &mut Bencher) {
92+
run_reconstruct_bench(bencher, 32, 4)
93+
}
94+
95+
#[bench]
96+
fn bench_reconstruct_32_8(bencher: &mut Bencher) {
97+
run_reconstruct_bench(bencher, 32, 8)
98+
}
99+
100+
#[bench]
101+
fn bench_reconstruct_32_16(bencher: &mut Bencher) {
102+
run_reconstruct_bench(bencher, 32, 16)
103+
}
104+
105+
#[bench]
106+
fn bench_reconstruct_32_32(bencher: &mut Bencher) {
107+
run_reconstruct_bench(bencher, 32, 32)
108+
}

src/core.rs

+39-36
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@ use smallvec::SmallVec;
99
use crate::errors::Error;
1010
use crate::errors::SBSError;
1111

12-
use crate::inversion_tree::InversionTree;
1312
use crate::matrix::Matrix;
1413

14+
use lru::LruCache;
15+
16+
#[cfg(feature = "std")]
17+
use parking_lot::Mutex;
18+
#[cfg(not(feature = "std"))]
19+
use spin::Mutex;
20+
1521
use super::Field;
1622
use super::ReconstructShard;
1723

24+
const DATA_DECODE_MATRIX_CACHE_CAPACITY: usize = 254;
25+
1826
// /// Parameters for parallelism.
1927
// #[derive(PartialEq, Debug, Clone, Copy)]
2028
// pub struct ParallelParam {
@@ -338,7 +346,7 @@ pub struct ReedSolomon<F: Field> {
338346
parity_shard_count: usize,
339347
total_shard_count: usize,
340348
matrix: Matrix<F>,
341-
tree: InversionTree<F>,
349+
data_decode_matrix_cache: Mutex<LruCache<Vec<usize>, Arc<Matrix<F>>>>,
342350
}
343351

344352
impl<F: Field> Clone for ReedSolomon<F> {
@@ -454,7 +462,7 @@ impl<F: Field> ReedSolomon<F> {
454462
parity_shard_count: parity_shards,
455463
total_shard_count: total_shards,
456464
matrix,
457-
tree: InversionTree::new(data_shards, parity_shards),
465+
data_decode_matrix_cache: Mutex::new(LruCache::new(DATA_DECODE_MATRIX_CACHE_CAPACITY)),
458466
})
459467
}
460468

@@ -691,40 +699,35 @@ impl<F: Field> ReedSolomon<F> {
691699
valid_indices: &[usize],
692700
invalid_indices: &[usize],
693701
) -> Arc<Matrix<F>> {
694-
// Attempt to get the cached inverted matrix out of the tree
695-
// based on the indices of the invalid rows.
696-
match self.tree.get_inverted_matrix(&invalid_indices) {
697-
// If the inverted matrix isn't cached in the tree yet we must
698-
// construct it ourselves and insert it into the tree for the
699-
// future. In this way the inversion tree is lazily loaded.
700-
None => {
701-
// Pull out the rows of the matrix that correspond to the
702-
// shards that we have and build a square matrix. This
703-
// matrix could be used to generate the shards that we have
704-
// from the original data.
705-
let mut sub_matrix = Matrix::new(self.data_shard_count, self.data_shard_count);
706-
for (sub_matrix_row, &valid_index) in valid_indices.iter().enumerate() {
707-
for c in 0..self.data_shard_count {
708-
sub_matrix.set(sub_matrix_row, c, self.matrix.get(valid_index, c));
709-
}
710-
}
711-
// Invert the matrix, so we can go from the encoded shards
712-
// back to the original data. Then pull out the row that
713-
// generates the shard that we want to decode. Note that
714-
// since this matrix maps back to the original data, it can
715-
// be used to create a data shard, but not a parity shard.
716-
let data_decode_matrix = Arc::new(sub_matrix.invert().unwrap());
717-
718-
// Cache the inverted matrix in the tree for future use keyed on the
719-
// indices of the invalid rows.
720-
self.tree
721-
.insert_inverted_matrix(&invalid_indices, &data_decode_matrix)
722-
.unwrap();
723-
724-
data_decode_matrix
702+
{
703+
let mut cache = self.data_decode_matrix_cache.lock();
704+
if let Some(entry) = cache.get(invalid_indices) {
705+
return entry.clone();
706+
}
707+
}
708+
// Pull out the rows of the matrix that correspond to the shards that
709+
// we have and build a square matrix. This matrix could be used to
710+
// generate the shards that we have from the original data.
711+
let mut sub_matrix = Matrix::new(self.data_shard_count, self.data_shard_count);
712+
for (sub_matrix_row, &valid_index) in valid_indices.iter().enumerate() {
713+
for c in 0..self.data_shard_count {
714+
sub_matrix.set(sub_matrix_row, c, self.matrix.get(valid_index, c));
725715
}
726-
Some(m) => m,
727716
}
717+
// Invert the matrix, so we can go from the encoded shards back to the
718+
// original data. Then pull out the row that generates the shard that
719+
// we want to decode. Note that since this matrix maps back to the
720+
// original data, it can be used to create a data shard, but not a
721+
// parity shard.
722+
let data_decode_matrix = Arc::new(sub_matrix.invert().unwrap());
723+
// Cache the inverted matrix for future use keyed on the indices of the
724+
// invalid rows.
725+
{
726+
let data_decode_matrix = data_decode_matrix.clone();
727+
let mut cache = self.data_decode_matrix_cache.lock();
728+
cache.put(Vec::from(invalid_indices), data_decode_matrix);
729+
}
730+
data_decode_matrix
728731
}
729732

730733
fn reconstruct_internal<T: ReconstructShard<F>>(
@@ -780,7 +783,7 @@ impl<F: Field> ReedSolomon<F> {
780783
//
781784
// The valid indices are used to construct the data decode matrix,
782785
// the invalid indices are used to key the data decode matrix
783-
// in the inversion tree.
786+
// in the data decode matrix cache.
784787
//
785788
// We only need exactly N valid indices, where N = `data_shard_count`,
786789
// as the data decode matrix is a N x N matrix, thus only needs

0 commit comments

Comments
 (0)