pub struct Coder { /* private fields */ }Expand description
An erasure coder for one (matrix) configuration: k source shards in,
p parity shards out, recovery from any k survivors.
Construction expands the parity coefficients into ISA-L-layout tables once; encode/update/recover then run with zero allocations on the data path (recover allocates only its small decode-matrix scratch).
Implementations§
Source§impl Coder
impl Coder
Sourcepub fn new(matrix: Matrix) -> Result<Self, MatrixError>
pub fn new(matrix: Matrix) -> Result<Self, MatrixError>
Build a coder from an encode matrix (rows = k + p, cols = k, top
block identity — what Matrix::reed_solomon / Matrix::cauchy
produce). A matrix with no parity rows is a dimension error.
This constructor uses the scalar kernel set — core carries no
detection machinery. The rusty_erasure facade’s coder() picks the
best SIMD set for the running CPU via Coder::with_kernels; prefer
it in applications.
Sourcepub fn with_kernels(
matrix: Matrix,
kernels: Kernels,
) -> Result<Self, MatrixError>
pub fn with_kernels( matrix: Matrix, kernels: Kernels, ) -> Result<Self, MatrixError>
Build a coder driving an explicit kernel set (see Kernels).
Sourcepub fn kernels(&self) -> &Kernels
pub fn kernels(&self) -> &Kernels
The kernel set this coder drives (name is useful for reporting).
Sourcepub fn gftbls(&self) -> &[u8] ⓘ
pub fn gftbls(&self) -> &[u8] ⓘ
The expanded parity tables, in THIS coder’s kernel-set format
(kernels().table_bytes per coefficient — ISA-L nibble layout for the
scalar/PSHUFB sets, affine matrices for GFNI). Exposed for the compat
layer and the conformance tests.
Sourcepub fn encode(
&self,
data: &[&[u8]],
parity: &mut [&mut [u8]],
) -> Result<(), CodeError>
pub fn encode( &self, data: &[&[u8]], parity: &mut [&mut [u8]], ) -> Result<(), CodeError>
Encode: k equal-length source shards in, p parity shards out
(overwritten). Byte-identical to ISA-L ec_encode_data.
Examples found in repository?
15fn main() {
16 let args: Vec<usize> = std::env::args()
17 .skip(1)
18 .filter_map(|a| a.parse().ok())
19 .collect();
20 let &[k, p, len, reps] = args.as_slice() else {
21 eprintln!("usage: scalar_baseline <k> <p> <shard_len> <reps>");
22 std::process::exit(2);
23 };
24
25 let coder = match Matrix::cauchy(k, p).and_then(Coder::new) {
26 Ok(c) => c,
27 Err(e) => {
28 eprintln!("bad config: {e}");
29 std::process::exit(2);
30 }
31 };
32
33 // Deterministic data (splitmix64) so every run encodes identical bytes.
34 let mut state: u64 = (k as u64) << 32 | (p as u64) << 16 | len as u64;
35 let mut next = move || {
36 state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
37 let mut z = state;
38 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
39 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
40 z ^ (z >> 31)
41 };
42 let data: Vec<Vec<u8>> = (0..k)
43 .map(|_| (0..len).map(|_| next() as u8).collect())
44 .collect();
45 let data_refs: Vec<&[u8]> = data.iter().map(|d| d.as_slice()).collect();
46 let mut parity = vec![vec![0u8; len]; p];
47
48 // Warmup (untimed), then the timed reps.
49 for _ in 0..3 {
50 let mut refs: Vec<&mut [u8]> = parity.iter_mut().map(|b| b.as_mut_slice()).collect();
51 coder.encode(&data_refs, &mut refs).expect("encode");
52 }
53 let mut per_rep_ns: Vec<u128> = Vec::with_capacity(reps);
54 let total = Instant::now();
55 for _ in 0..reps {
56 let t = Instant::now();
57 let mut refs: Vec<&mut [u8]> = parity.iter_mut().map(|b| b.as_mut_slice()).collect();
58 coder
59 .encode(&data_refs, black_box(&mut refs))
60 .expect("encode");
61 per_rep_ns.push(t.elapsed().as_nanos());
62 black_box(&parity);
63 }
64 let wall = total.elapsed();
65
66 let checksum = parity.iter().flatten().fold(0u8, |a, &b| a ^ b);
67 per_rep_ns.sort_unstable();
68 let src_bytes = (k * len) as u128 * reps as u128;
69 let mul_count = (k * p * len) as u128 * reps as u128;
70 println!("cell k={k} p={p} len={len} reps={reps}");
71 println!("work: source_bytes={src_bytes} table_muls={mul_count} checksum={checksum:#04x}");
72 println!(
73 "wall: total_ms={} rep_min_us={} rep_median_us={}",
74 wall.as_millis(),
75 per_rep_ns.first().unwrap_or(&0) / 1000,
76 per_rep_ns.get(reps / 2).unwrap_or(&0) / 1000,
77 );
78}Sourcepub fn update(
&self,
shard_index: usize,
data: &[u8],
parity: &mut [&mut [u8]],
) -> Result<(), CodeError>
pub fn update( &self, shard_index: usize, data: &[u8], parity: &mut [&mut [u8]], ) -> Result<(), CodeError>
Incremental encode: fold ONE source shard (index shard_index) into
all parity shards. Starting from zeroed parity buffers and calling this
once per source (any order) yields byte-identical output to
Coder::encode — ISA-L ec_encode_data_update semantics.
Sourcepub fn verify(
&self,
data: &[&[u8]],
parity: &[&[u8]],
) -> Result<bool, CodeError>
pub fn verify( &self, data: &[&[u8]], parity: &[&[u8]], ) -> Result<bool, CodeError>
Check that parity is consistent with data. Ok(true) means every
parity shard matches a fresh encode.
Sourcepub fn decode_plan(
&self,
present: &[bool],
rebuild: &[usize],
) -> Result<DecodePlan, RecoverError>
pub fn decode_plan( &self, present: &[bool], rebuild: &[usize], ) -> Result<DecodePlan, RecoverError>
Prepare a reusable decode plan for one loss pattern: which shards are
present (present[i]), and which indices to rebuild. The expensive,
data-independent work — survivor selection, submatrix inversion,
coefficient composition, table expansion — happens ONCE here;
Coder::recover_with then rebuilds any number of stripes with that
pattern at pure kernel cost (repair jobs and steady-state degraded
reads reuse one plan across every stripe).
Sourcepub fn recover_with(
&self,
plan: &DecodePlan,
shards: &[Option<&[u8]>],
out: &mut [&mut [u8]],
) -> Result<(), RecoverError>
pub fn recover_with( &self, plan: &DecodePlan, shards: &[Option<&[u8]>], out: &mut [&mut [u8]], ) -> Result<(), RecoverError>
Rebuild one stripe with a prepared DecodePlan — pure kernel cost,
no matrix work, no table expansion, one small scratch collection.
Sourcepub fn recover(
&self,
shards: &[Option<&[u8]>],
rebuild: &[usize],
out: &mut [&mut [u8]],
) -> Result<(), RecoverError>
pub fn recover( &self, shards: &[Option<&[u8]>], rebuild: &[usize], out: &mut [&mut [u8]], ) -> Result<(), RecoverError>
Rebuild shards from survivors.
shards is the full stripe in index order — k sources then p
parity — with None for anything lost. rebuild names the shard
indices to reconstruct (source or parity), and out supplies one
equal-length buffer per rebuild target. Any k present shards suffice;
fewer is RecoverError::TooManyMissing.
For many stripes with one loss pattern, build a Coder::decode_plan
once and use Coder::recover_with — this one-shot form re-derives
the decode matrix every call.