Skip to content

Commit 667b5e5

Browse files
committed
Optimize allocations and memory copying in subspace-archiving with special wrapper and a bit of unsafe
1 parent f97547a commit 667b5e5

File tree

2 files changed

+134
-49
lines changed

2 files changed

+134
-49
lines changed

crates/subspace-archiving/src/archiver.rs

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@
1212
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
15+
16+
mod record_shards;
17+
1518
extern crate alloc;
1619

20+
use crate::archiver::record_shards::RecordShards;
1721
use crate::merkle_tree::{MerkleTree, Witness};
18-
use crate::utils;
19-
use crate::utils::{Gf16Element, GF_16_ELEMENT_BYTES};
22+
use crate::utils::GF_16_ELEMENT_BYTES;
2023
use alloc::borrow::Cow;
2124
use alloc::collections::VecDeque;
2225
use alloc::vec;
2326
use alloc::vec::Vec;
2427
use core::cmp::Ordering;
25-
use core::iter;
2628
use parity_scale_codec::{Compact, CompactLen, Decode, Encode};
2729
use reed_solomon_erasure::galois_16::ReedSolomon;
2830
use subspace_core_primitives::objects::{
@@ -135,6 +137,9 @@ pub enum ArchiverInstantiationError {
135137
error("Segment size is not bigger than record size")
136138
)]
137139
SegmentSizeTooSmall,
140+
/// Segment size must be multiple of two
141+
#[cfg_attr(feature = "thiserror", error("Segment size must be multiple of two"))]
142+
SegmentSizeNotMultipleOfTwo,
138143
/// Segment size is not a multiple of record size
139144
#[cfg_attr(
140145
feature = "thiserror",
@@ -220,6 +225,9 @@ impl Archiver {
220225
if segment_size <= record_size {
221226
return Err(ArchiverInstantiationError::SegmentSizeTooSmall);
222227
}
228+
if segment_size % GF_16_ELEMENT_BYTES != 0 {
229+
return Err(ArchiverInstantiationError::SegmentSizeNotMultipleOfTwo);
230+
}
223231
if segment_size % record_size != 0 {
224232
return Err(ArchiverInstantiationError::SegmentSizesNotMultipleOfRecordSize);
225233
}
@@ -611,64 +619,54 @@ impl Archiver {
611619
}
612620
corrected_object_mapping
613621
};
614-
let segment = {
615-
let mut segment = segment.encode();
616-
// Add a few bytes of padding if needed to get constant size (caused by compact length
617-
// encoding of scale codec)
618-
assert!(self.segment_size >= segment.len());
619-
segment.resize(self.segment_size, 0u8);
620-
segment
622+
let mut record_shards = {
623+
// Allocate record shards that can hold both data and parity record shards (hence `*2`)
624+
let mut record_shards =
625+
RecordShards::new(self.segment_size / self.record_size * 2, self.record_size);
626+
segment.encode_to(&mut record_shards);
627+
record_shards
621628
};
622629

623-
let data_shards: Vec<Vec<Gf16Element>> = segment
624-
.chunks_exact(self.record_size)
625-
.map(utils::slice_to_arrays)
626-
.collect();
627-
628630
drop(segment);
629631

630-
let mut parity_shards: Vec<Vec<Gf16Element>> =
631-
iter::repeat(vec![Gf16Element::default(); self.record_size / 2])
632-
.take(data_shards.len())
633-
.collect();
632+
let mut record_shards_slices = record_shards.as_mut_slices();
634633

635634
// Apply erasure coding to to create parity shards/records
636635
self.reed_solomon
637-
.encode_sep(&data_shards, &mut parity_shards)
638-
.expect("Encoding is running with fixed parameters and should never fail");
636+
.encode(&mut record_shards_slices)
637+
.expect("Encoding is running with fixed parameters and should never fail; qed");
639638

640-
let mut pieces = vec![0u8; (data_shards.len() + parity_shards.len()) * PIECE_SIZE];
641-
// Combine data and parity records back into flat vector of pieces (witness part of piece is
642-
// still zeroes after this and is filled later)
643-
pieces
644-
.chunks_exact_mut(PIECE_SIZE)
645-
.zip(data_shards.into_iter().chain(parity_shards))
646-
.flat_map(|(piece, shard)| {
647-
piece
648-
.chunks_exact_mut(GF_16_ELEMENT_BYTES)
649-
.zip(shard.into_iter())
650-
})
651-
.for_each(|(piece_chunk, shard_chunk)| {
652-
piece_chunk.copy_from_slice(&shard_chunk);
653-
});
639+
let mut pieces = FlatPieces::new(record_shards_slices.len());
640+
drop(record_shards_slices);
654641

655642
// Build a Merkle tree over all records
656643
let merkle_tree = MerkleTree::from_data(
657-
pieces
658-
.chunks_exact(PIECE_SIZE)
659-
.map(|piece| &piece[..self.record_size]),
644+
record_shards
645+
.as_bytes()
646+
.as_ref()
647+
.chunks_exact(self.record_size),
660648
);
661649

662-
// Fill witnesses (Merkle proofs) in pieces created earlier
650+
// Combine data and parity records back into flat vector of pieces along with corresponding
651+
// witnesses (Merkle proofs) created above.
663652
pieces
664-
.chunks_exact_mut(PIECE_SIZE)
653+
.as_pieces_mut()
665654
.enumerate()
666-
.for_each(|(position, piece)| {
667-
let witness = merkle_tree
668-
.get_witness(position)
669-
.expect("We use the same indexes as during Merkle tree creation; qed");
670-
671-
piece[self.record_size..].copy_from_slice(&witness);
655+
.zip(
656+
record_shards
657+
.as_bytes()
658+
.as_ref()
659+
.chunks_exact(self.record_size),
660+
)
661+
.for_each(|((position, piece), shard_chunk)| {
662+
let (record_part, witness_part) = piece.split_at_mut(self.record_size);
663+
664+
record_part.copy_from_slice(shard_chunk);
665+
witness_part.copy_from_slice(
666+
&merkle_tree
667+
.get_witness(position)
668+
.expect("We use the same indexes as during Merkle tree creation; qed"),
669+
);
672670
});
673671

674672
// Now produce root block
@@ -689,9 +687,7 @@ impl Archiver {
689687

690688
ArchivedSegment {
691689
root_block,
692-
pieces: pieces
693-
.try_into()
694-
.expect("Pieces length is correct as created above; qed"),
690+
pieces,
695691
object_mapping,
696692
}
697693
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::utils::{Gf16Element, GF_16_ELEMENT_BYTES};
2+
use std::io;
3+
use std::mem::ManuallyDrop;
4+
5+
/// Wrapper data structure for record shards that correspond to the same recorded history segment
6+
/// for more convenient management.
7+
///
8+
/// Allows to accessing underlying data both as list of shards for erasure coding and regular slice
9+
/// of bytes for other purposes, also implements [`std::io::Write`] so that it can be used with
10+
/// `parity-scale-codec` to write encoded data right into [`RecordShards`].
11+
pub(super) struct RecordShards {
12+
shards: Vec<Gf16Element>,
13+
cursor: usize,
14+
/// Shard size in bytes
15+
shard_size: usize,
16+
}
17+
18+
impl io::Write for RecordShards {
19+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
20+
// SAFETY:
21+
// Lifetime is the same as source, de-allocation is prevented with `ManuallyDrop`, no
22+
// re-allocation will happen so interpreting one vector as different vector is fine.
23+
unsafe {
24+
let mut target = ManuallyDrop::new(Vec::from_raw_parts(
25+
self.shards.as_mut_ptr() as *mut u8,
26+
self.shards.len() * GF_16_ELEMENT_BYTES,
27+
self.shards.len() * GF_16_ELEMENT_BYTES,
28+
));
29+
30+
let written_bytes = target[self.cursor..].as_mut().write(buf)?;
31+
32+
self.cursor += written_bytes;
33+
34+
Ok(written_bytes)
35+
}
36+
}
37+
38+
fn flush(&mut self) -> io::Result<()> {
39+
Ok(())
40+
}
41+
}
42+
43+
impl RecordShards {
44+
/// Create new `Shards` struct for specified number of shards and shard size in bytes.
45+
///
46+
/// Panics if shard size is not multiple of 2.
47+
pub(super) fn new(number_of_shards: usize, shard_size: usize) -> Self {
48+
assert_eq!(shard_size % GF_16_ELEMENT_BYTES, 0);
49+
50+
let mut shards = Vec::with_capacity(number_of_shards * shard_size / GF_16_ELEMENT_BYTES);
51+
shards.resize(shards.capacity(), Gf16Element::default());
52+
53+
Self {
54+
shards,
55+
cursor: 0,
56+
shard_size,
57+
}
58+
}
59+
60+
/// Access internal record shards as contiguous memory slice.
61+
pub(super) fn as_bytes(&mut self) -> impl AsRef<[u8]> + '_ {
62+
// SAFETY:
63+
// Returned lifetime is the same as source, de-allocation is prevented with `ManuallyDrop`
64+
// and specific type is erased from API, so nothing except reading bytes is possible.
65+
unsafe {
66+
/// Private wrapper just so that `AsRef<[u8]>` can be implemented on it.
67+
struct AsRefRecordShards(ManuallyDrop<Vec<u8>>);
68+
69+
impl AsRef<[u8]> for AsRefRecordShards {
70+
fn as_ref(&self) -> &[u8] {
71+
&self.0
72+
}
73+
}
74+
75+
AsRefRecordShards(ManuallyDrop::new(Vec::from_raw_parts(
76+
self.shards.as_mut_ptr() as *mut u8,
77+
self.shards.len() * GF_16_ELEMENT_BYTES,
78+
self.shards.len() * GF_16_ELEMENT_BYTES,
79+
)))
80+
}
81+
}
82+
83+
/// Access internal record shards as vector of mutable shards.
84+
pub(super) fn as_mut_slices(&mut self) -> Vec<&mut [Gf16Element]> {
85+
self.shards
86+
.chunks_exact_mut(self.shard_size / GF_16_ELEMENT_BYTES)
87+
.collect()
88+
}
89+
}

0 commit comments

Comments
 (0)