zstd_complete/dictionary/
mod.rs1mod cover;
26mod frequency;
27mod reservoir;
28
29use crate::dictionary::reservoir::create_sample;
30use core::cmp::Reverse;
31use cover::*;
32use std::{
33 boxed::Box,
34 collections::{BinaryHeap, HashMap},
35 fs::{self, File},
36 io::{self, Read},
37 path::{Path, PathBuf},
38 vec::Vec,
39};
40
41pub(super) struct DictParams {
46 pub segment_size: u32,
56}
57
58pub fn create_raw_dict_from_dir<P: AsRef<Path>, W: io::Write>(
76 path: P,
77 output: &mut W,
78 dict_size: usize,
79) -> Result<(), io::Error> {
80 let mut file_paths: Vec<PathBuf> = Vec::new();
82 let dir: fs::ReadDir = fs::read_dir(path)?;
83 fn recurse_read(dir: fs::ReadDir, file_paths: &mut Vec<PathBuf>) -> Result<(), io::Error> {
84 for entry in dir {
85 let entry = entry?;
86 if entry.file_type()?.is_dir() {
87 recurse_read(fs::read_dir(entry.path())?, file_paths)?;
88 } else {
89 file_paths.push(entry.path());
90 }
91 }
92 Ok(())
93 }
94 recurse_read(dir, &mut file_paths)?;
95
96 let mut total_file_len: u64 = 0;
98 let mut file_handles: Vec<fs::File> = Vec::new();
99 for path in file_paths {
100 let handle = File::open(path)?;
101 total_file_len += handle.metadata()?.len();
102 file_handles.push(handle);
103 }
104 let empty_reader: Box<dyn Read> = Box::new(io::empty());
105 let chained_files = file_handles
106 .iter()
107 .fold(empty_reader, |acc, reader| Box::new(acc.chain(reader)));
108
109 create_raw_dict_from_source(chained_files, total_file_len as usize, output, dict_size);
111 Ok(())
112}
113
114pub fn create_raw_dict_from_source<R: io::Read, W: io::Write>(
128 mut source: R,
129 source_size: usize,
130 output: &mut W,
131 dict_size: usize,
132) {
133 if source_size < 16 {
134 let mut source = source;
135 let mut buf = Vec::new();
136 source
137 .read_to_end(&mut buf)
138 .expect("Could not read from source");
139 output.write_all(&buf).expect("Could not write to output");
140 return;
141 }
142 vprintln!("create_dict: creating {dict_size} byte dict from {source_size} byte source");
143 if dict_size == 0 {
144 return;
145 }
146
147 let mut source_data = Vec::with_capacity(source_size);
148 source
149 .read_to_end(&mut source_data)
150 .expect("can read input");
151 if source_data.is_empty() {
152 return;
153 }
154
155 if source_data.len() < K {
156 output
157 .write_all(&source_data[..usize::min(dict_size, source_data.len())])
158 .expect("can write to output");
159 return;
160 }
161
162 let params = DictParams { segment_size: 2048 };
163 let segment_size = params.segment_size as usize;
164 let num_segments = usize::max(1, source_data.len().div_ceil(segment_size));
165 let sample_divisor = usize::min(usize::max(1, source_data.len() / (2 * num_segments)), 256);
169 let sample_size = usize::max(K, source_data.len() / sample_divisor);
170 vprintln!("create_dict: creating {sample_size} byte sample of collection");
171 let collection_sample = create_sample(&mut source_data.as_slice(), sample_size);
172
173 let mut pool: BinaryHeap<Reverse<Segment>> = BinaryHeap::new();
179 let (_, epoch_kmers) = compute_epoch_info(¶ms, dict_size, source_data.len() / K);
180 let epoch_size = usize::min(source_data.len(), usize::max(K, epoch_kmers * K));
181 let num_epochs = source_data.len().div_ceil(epoch_size);
182 vprintln!("create_dict: computed epoch info, using {num_epochs} epochs of {epoch_size} bytes");
183 let mut ctx = Context {
184 frequencies: HashMap::with_capacity(epoch_size / K),
185 };
186 for (epoch_index, current_epoch) in source_data.chunks(epoch_size).enumerate() {
189 let Some(best_segment) =
190 pick_best_segment(¶ms, &mut ctx, current_epoch, &collection_sample)
191 else {
192 continue;
193 };
194 vprintln!(
195 "\tcreate_dict: epoch {}/{} has best segment score {}",
196 epoch_index + 1,
197 num_epochs,
198 best_segment.score
199 );
200 pool.push(Reverse(best_segment));
201 ctx.frequencies.clear();
203 }
204 vprintln!(
205 "create_dict: {num_epochs} epochs written, writing {} segments",
206 pool.len()
207 );
208 let mut remaining = dict_size;
211 while let Some(segment) = pool.pop() {
212 if remaining == 0 {
213 break;
214 }
215 let bytes_to_write = usize::min(remaining, segment.0.raw.len());
216 output
217 .write_all(&segment.0.raw[..bytes_to_write])
218 .expect("can write to output");
219 remaining -= bytes_to_write;
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::create_raw_dict_from_source;
226 use std::vec::Vec;
227
228 #[test]
229 fn raw_dict_builder_scores_epochs_after_sampling() {
230 let mut source = Vec::new();
231 for _ in 0..512 {
232 source.extend_from_slice(b"aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb");
233 source.extend_from_slice(b"ccccccccccccccccdddddddddddddddd");
234 }
235 let mut dictionary = Vec::new();
236
237 create_raw_dict_from_source(source.as_slice(), source.len(), &mut dictionary, 1024);
238
239 assert!(!dictionary.is_empty());
240 assert!(dictionary.len() <= 1024);
241 }
242
243 #[test]
244 fn raw_dict_builder_handles_tiny_sources() {
245 let source = b"tiny";
246 let mut dictionary = Vec::new();
247
248 create_raw_dict_from_source(source.as_slice(), source.len(), &mut dictionary, 1024);
249
250 assert_eq!(dictionary, source);
251 }
252}
253
254#[test]
255fn create_raw_dict_from_source_no_panics_on_small_input() {
256 use std::io::Cursor;
257
258 for size in 0..1024 {
259 let input = alloc::vec![b'A'; size];
260 let mut output = Vec::new();
261
262 create_raw_dict_from_source(Cursor::new(input.clone()), input.len(), &mut output, 64);
263 }
264}