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}