deflate/matching.rs
1use std::cmp;
2
3use chained_hash_table::{ChainedHashTable, WINDOW_SIZE};
4use huffman_table;
5
6const MAX_MATCH: usize = huffman_table::MAX_MATCH as usize;
7#[cfg(test)]
8const MIN_MATCH: usize = huffman_table::MIN_MATCH as usize;
9
10
11/// Get the length of the checked match
12/// The function returns number of bytes at and including `current_pos` that are the same as the
13/// ones at `pos_to_check`
14#[inline]
15pub fn get_match_length(data: &[u8], current_pos: usize, pos_to_check: usize) -> usize {
16 // Unsafe version using unaligned loads for comparison.
17 // Faster when benching the matching function alone,
18 // but not as significant when running the full thing.
19/*
20 type Comp = u64;
21
22 use std::mem::size_of;
23
24 let max = cmp::min(data.len() - current_pos, MAX_MATCH);
25 let mut left = max;
26 let s = size_of::<Comp>();
27
28 unsafe {
29 let mut cur = data.as_ptr().offset(current_pos as isize);
30 let mut tc = data.as_ptr().offset(pos_to_check as isize);
31 while left >= s &&
32 (*(cur as *const Comp) == *(tc as *const Comp)) {
33 left -= s;
34 cur = cur.offset(s as isize);
35 tc = tc.offset(s as isize);
36 }
37 while left > 0 && *cur == *tc {
38 left -= 1;
39 cur = cur.offset(1);
40 tc = tc.offset(1);
41 }
42 }
43
44 max - left
45*/
46
47 // Slightly faster than naive in single bench.
48 // Does not use unaligned loads.
49 // let l = cmp::min(MAX_MATCH, data.len() - current_pos);
50
51 // let a = unsafe{&data.get_unchecked(current_pos..current_pos + l)};
52 // let b = unsafe{&data.get_unchecked(pos_to_check..)};
53
54 // let mut len = 0;
55
56 // for (l, r) in a
57 // .iter()
58 // .zip(b.iter()) {
59 // if *l == *r {
60 // len += 1;
61 // continue;
62 // } else {
63 // break;
64 // }
65 // }
66 // len as usize
67
68 // Naive version
69 data[current_pos..]
70 .iter()
71 .zip(data[pos_to_check..].iter())
72 .take(MAX_MATCH)
73 .take_while(|&(&a, &b)| a == b)
74 .count()
75}
76
77/// Try finding the position and length of the longest match in the input data.
78/// # Returns
79/// (length, distance from position)
80/// If no match is found that was better than `prev_length` or at all, or we are at the start,
81/// the length value returned will be 2.
82///
83/// # Arguments:
84/// `data`: The data to search in.
85/// `hash_table`: Hash table to use for searching.
86/// `position`: The position in the data to match against.
87/// `prev_length`: The length of the previous `longest_match` check to compare against.
88/// `max_hash_checks`: The maximum number of matching hash chain positions to check.
89pub fn longest_match(
90 data: &[u8],
91 hash_table: &ChainedHashTable,
92 position: usize,
93 prev_length: usize,
94 max_hash_checks: u16,
95) -> (usize, usize) {
96
97 // debug_assert_eq!(position, hash_table.current_head() as usize);
98
99 // If we already have a match at the maximum length,
100 // or we can't grow further, we stop here.
101 if prev_length >= MAX_MATCH || position + prev_length >= data.len() {
102 return (0, 0);
103 }
104
105 let limit = if position > WINDOW_SIZE {
106 position - WINDOW_SIZE
107 } else {
108 0
109 };
110
111 // Make sure the length is at least one to simplify the matching code, as
112 // otherwise the matching code might underflow.
113 let prev_length = cmp::max(prev_length, 1);
114
115 let max_length = cmp::min(data.len() - position, MAX_MATCH);
116
117 // The position in the hash chain we are currently checking.
118 let mut current_head = position;
119
120 // The best match length we've found so far, and it's distance.
121 let mut best_length = prev_length;
122 let mut best_distance = 0;
123
124 // The position of the previous value in the hash chain.
125 let mut prev_head;
126
127 for _ in 0..max_hash_checks {
128 prev_head = current_head;
129 current_head = hash_table.get_prev(current_head) as usize;
130 if current_head >= prev_head || current_head < limit {
131 // If the current hash chain value refers to itself, or is referring to
132 // a value that's higher (we only move backwars through the chain),
133 // we are at the end and can stop.
134 break;
135 }
136
137 // We only check further if the match length can actually increase
138 // Checking if the end byte and the potential next byte matches is generally
139 // more likely to give a quick answer rather than checking from the start first, given
140 // that the hashes match.
141 // If there is no previous match, best_length will be 1 and the two first bytes will
142 // be checked instead.
143 // Since we've made sure best_length is always at least 1, this shouldn't underflow.
144 if data[position + best_length - 1..position + best_length + 1] ==
145 data[current_head + best_length - 1..current_head + best_length + 1]
146 {
147 // Actually check how many bytes match.
148 // At the moment this will check the two bytes we just checked again,
149 // though adding code for skipping these bytes may not result in any speed
150 // gain due to the added complexity.
151 let length = get_match_length(data, position, current_head);
152 if length > best_length {
153 best_length = length;
154 best_distance = position - current_head;
155 if length == max_length {
156 // We are at the max length, so there is no point
157 // searching any longer
158 break;
159 }
160 }
161 }
162 }
163
164 if best_length > prev_length {
165 (best_length, best_distance)
166 } else {
167 (0, 0)
168 }
169}
170
171/// Try finding the position and length of the longest match in the input data using fast zlib
172/// hash skipping algorithm.
173/// # Returns
174/// (length, distance from position)
175/// If no match is found that was better than `prev_length` or at all, or we are at the start,
176/// the length value returned will be 2.
177///
178/// # Arguments:
179/// `data`: The data to search in.
180/// `hash_table`: Hash table to use for searching.
181/// `position`: The position in the data to match against.
182/// `prev_length`: The length of the previous `longest_match` check to compare against.
183/// `max_hash_checks`: The maximum number of matching hash chain positions to check.
184#[cfg(test)]
185pub fn longest_match_fast(
186 data: &[u8],
187 hash_table: &ChainedHashTable,
188 position: usize,
189 prev_length: usize,
190 max_hash_checks: u16,
191) -> (usize, usize) {
192
193 // debug_assert_eq!(position, hash_table.current_head() as usize);
194
195 // If we already have a match at the maximum length,
196 // or we can't grow further, we stop here.
197 if prev_length >= MAX_MATCH || position + prev_length >= data.len() {
198 return (0, 0);
199 }
200
201 let limit = if position > WINDOW_SIZE {
202 position - WINDOW_SIZE
203 } else {
204 0
205 };
206
207 // Make sure the length is at least one to simplify the matching code, as
208 // otherwise the matching code might underflow.
209 let prev_length = cmp::max(prev_length, 1);
210
211 let max_length = cmp::min((data.len() - position), MAX_MATCH);
212
213 // The position in the hash chain we are currently checking.
214 let mut current_head = position;
215
216 // The best match length we've found so far, and it's distance.
217 let mut best_length = prev_length;
218 let mut best_distance = 0;
219 // The offset from the start of the match of the hash chain we are traversing.
220 let mut offset = 0;
221
222 // The position of the previous value in the hash chain.
223 let mut prev_head;
224
225 for _ in 0..max_hash_checks {
226 prev_head = current_head;
227 current_head = hash_table.get_prev(current_head) as usize;
228 if current_head >= prev_head || current_head < limit + offset {
229 // If the current hash chain value refers to itself, or is referring to
230 // a value that's higher (we only move backwars through the chain),
231 // we are at the end and can stop.
232 break;
233 }
234
235 let offset_head = current_head - offset;
236
237 // We only check further if the match length can actually increase
238 // Checking if the end byte and the potential next byte matches is generally
239 // more likely to give a quick answer rather than checking from the start first, given
240 // that the hashes match.
241 // If there is no previous match, best_length will be 1 and the two first bytes will
242 // be checked instead.
243 // Since we've made sure best_length is always at least 1, this shouldn't underflow.
244 if data[position + best_length - 1..position + best_length + 1] ==
245 data[offset_head + best_length - 1..offset_head + best_length + 1]
246 {
247 // Actually check how many bytes match.
248 // At the moment this will check the two bytes we just checked again,
249 // though adding code for skipping these bytes may not result in any speed
250 // gain due to the added complexity.
251 let length = get_match_length(data, position, offset_head);
252 if length > best_length {
253 best_length = length;
254 best_distance = position - offset_head;
255 if length == max_length {
256 // We are at the max length, so there is no point
257 // searching any longer
258 break;
259 }
260
261 // Find the position in the match where the next has position is the furthest away.
262 // By moving to a different hash chain we can potentially skip a lot of checks,
263 // saving time.
264 // We avoid doing this for matches that extend past the starting position, as
265 // those will contain positions that are not in the hash table yet.
266 if best_distance > best_length {
267 offset = hash_table.farthest_next(offset_head, length);
268 current_head = offset_head + offset;
269 }
270 }
271 }
272 }
273
274 if best_length > prev_length {
275 (best_length, best_distance)
276 } else {
277 (0, 0)
278 }
279}
280
281// Get the longest match from the current position of the hash table.
282#[inline]
283#[cfg(test)]
284pub fn longest_match_current(data: &[u8], hash_table: &ChainedHashTable) -> (usize, usize) {
285 use compression_options::MAX_HASH_CHECKS;
286 longest_match(
287 data,
288 hash_table,
289 hash_table.current_head() as usize,
290 MIN_MATCH as usize - 1,
291 MAX_HASH_CHECKS,
292 )
293}
294
295#[cfg(test)]
296mod test {
297 use chained_hash_table::{filled_hash_table, HASH_BYTES, ChainedHashTable};
298 use super::{get_match_length, longest_match, longest_match_fast};
299
300 /// Test that match lengths are calculated correctly
301 #[test]
302 fn match_length() {
303 let test_arr = [5u8, 5, 5, 5, 5, 9, 9, 2, 3, 5, 5, 5, 5, 5];
304 let l = get_match_length(&test_arr, 9, 0);
305 assert_eq!(l, 5);
306 let l2 = get_match_length(&test_arr, 9, 7);
307 assert_eq!(l2, 0);
308 let l3 = get_match_length(&test_arr, 10, 0);
309 assert_eq!(l3, 4);
310 }
311
312 /// Test that we get the longest of the matches
313 #[test]
314 fn get_longest_match() {
315 let test_data = b"xTest data, Test_data,zTest data";
316 let hash_table = filled_hash_table(&test_data[..23 + 1 + HASH_BYTES - 1]);
317
318 let (length, distance) = super::longest_match_current(test_data, &hash_table);
319
320 // We check that we get the longest match, rather than the shorter, but closer one.
321 assert_eq!(distance, 22);
322 assert_eq!(length, 9);
323 let test_arr2 = [
324 10u8,
325 10,
326 10,
327 10,
328 10,
329 10,
330 10,
331 10,
332 2,
333 3,
334 5,
335 10,
336 10,
337 10,
338 10,
339 10,
340 ];
341 let hash_table = filled_hash_table(&test_arr2[..HASH_BYTES + 1 + 1 + 2]);
342 let (length, distance) = super::longest_match_current(&test_arr2, &hash_table);
343
344 assert_eq!(distance, 1);
345 assert_eq!(length, 4);
346 }
347
348 /// Make sure we can get a match at index zero
349 #[test]
350 fn match_index_zero() {
351 let test_data = b"AAAAAAA";
352
353 let mut hash_table = ChainedHashTable::from_starting_values(test_data[0], test_data[1]);
354 for (n, &b) in test_data[2..5].iter().enumerate() {
355 hash_table.add_hash_value(n, b);
356 }
357
358 let (match_length, match_dist) = longest_match(test_data, &hash_table, 1, 0, 4096);
359
360 assert_eq!(match_dist, 1);
361 assert!(match_length == 6);
362 }
363
364 /// Test for fast_zlib algorithm.
365 /// Check that it doesn't give worse matches than the default one.
366 /// ignored by default as it's slow, and best ran in release mode.
367 #[ignore]
368 #[test]
369 fn fast_match_at_least_equal() {
370 use test_utils::get_test_data;
371 for start_pos in 10000..50000 {
372 const NUM_CHECKS: u16 = 400;
373 let data = get_test_data();
374 let hash_table = filled_hash_table(&data[..start_pos + 1]);
375 let pos = hash_table.current_head() as usize;
376
377 let naive_match = longest_match(&data[..], &hash_table, pos, 0, NUM_CHECKS);
378 let fast_match = longest_match_fast(&data[..], &hash_table, pos, 0, NUM_CHECKS);
379
380 if fast_match.0 > naive_match.0 {
381 println!("Fast match found better match!");
382 }
383
384 assert!(fast_match.0 >= naive_match.0,
385 "naive match had better length! start_pos: {}, naive: {:?}, fast {:?}"
386 , start_pos, naive_match, fast_match);
387 assert!(fast_match.1 >= naive_match.1,
388 "naive match had better dist! start_pos: {} naive {:?}, fast {:?}"
389 , start_pos, naive_match, fast_match);
390 }
391
392 }
393}
394
395
396#[cfg(all(test, feature = "benchmarks"))]
397mod bench {
398 use test_std::Bencher;
399 use test_utils::get_test_data;
400 use chained_hash_table::filled_hash_table;
401 use super::{longest_match, longest_match_fast};
402 #[bench]
403 fn matching(b: &mut Bencher) {
404 const POS: usize = 29000;
405 let data = get_test_data();
406 let hash_table = filled_hash_table(&data[..POS + 1]);
407 let pos = hash_table.current_head() as usize;
408 println!("M: {:?}", longest_match(&data[..], &hash_table, pos, 0, 4096));
409 b.iter( ||
410 longest_match(&data[..], &hash_table, pos, 0, 4096)
411 );
412 }
413
414 #[bench]
415 fn fast_matching(b: &mut Bencher) {
416 const POS: usize = 29000;
417 let data = get_test_data();
418 let hash_table = filled_hash_table(&data[..POS + 1]);
419 let pos = hash_table.current_head() as usize;
420 println!("M: {:?}", longest_match_fast(&data[..], &hash_table, pos, 0, 4096));
421 b.iter( ||
422 longest_match_fast(&data[..], &hash_table, pos, 0, 4096)
423 );
424 }
425}