Skip to content

Commit b9823e6

Browse files
committed
Optimise by filtering out empty values.
The `fits` function (and to a lesser extent `apply`) previously did vast amounts of pointless work on empty values that could not succeed. This commit does something very simple: it pre-filters out all the empty values so that `fits` has a lot less work to do. On small grammars, the quantity of pointless work probably wasn't very noticeable, but on large grammars like Postgresql's, it became punishing. On my machine this commit takes nimbleparse's running time down from 101s to 3s.
1 parent 0b8c682 commit b9823e6

File tree

1 file changed

+24
-17
lines changed

1 file changed

+24
-17
lines changed

src/lib.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,25 @@ fn compress<T: Clone + Copy + PartialEq>(
174174
let mut r = Vec::new(); // Result vector
175175
r.resize(row_length, empty_val);
176176

177-
let mut dv = Vec::new(); // displacement vector
178-
dv.resize(sorted.len(), 0);
177+
let mut dv = vec![0; sorted.len()]; // displacement vector
179178

179+
let mut tmp = Vec::new();
180180
for s in sorted {
181-
let slice = &vec[s * row_length..(s + 1) * row_length];
181+
// The row we're about to iterate over typically contains mostly empty values that can
182+
// never succeed with `fits`. We pre-filter out all those empty values up-front, such that
183+
// `tmp` contains `(index, non-empty-value)` pairs that we can then pass to `fits`. Because
184+
// this is such a tight loop, we reuse the same `Vec` to avoid repeated allocations.
185+
tmp.clear();
186+
for (i, v) in vec[s * row_length..(s + 1) * row_length].iter().enumerate() {
187+
if *v != empty_val {
188+
tmp.push((i, v));
189+
}
190+
}
191+
182192
let mut d = 0; // displacement value
183193
loop {
184-
if fits(slice, &r, d, empty_val) {
185-
apply(slice, &mut r, d, empty_val);
194+
if fits(tmp.as_slice(), &r, d, empty_val) {
195+
apply(tmp.as_slice(), &mut r, d);
186196
dv[*s] = d;
187197
break;
188198
} else {
@@ -196,27 +206,24 @@ fn compress<T: Clone + Copy + PartialEq>(
196206
(r, dv)
197207
}
198208

199-
fn fits<T: PartialEq>(v: &[T], target: &[T], d: usize, empty_val: T) -> bool {
200-
for i in 0..v.len() {
201-
if v[i] != empty_val && target[d + i] != empty_val && target[d + i] != v[i] {
209+
/// `v` is an array of `(index, non-empty_val)` pairs.
210+
fn fits<T: PartialEq>(v: &[(usize, &T)], target: &[T], d: usize, empty_val: T) -> bool {
211+
for (i, x) in v {
212+
if target[d + i] != empty_val && target[d + i] != **x {
202213
return false;
203214
}
204215
}
205216
true
206217
}
207218

208-
fn apply<T: Copy + PartialEq>(v: &[T], target: &mut [T], d: usize, empty_val: T) {
209-
for i in 0..v.len() {
210-
if v[i] != empty_val {
211-
target[d + i] = v[i]
212-
}
219+
/// `v` is an array of `(index, non-empty_val)` pairs.
220+
fn apply<T: Copy + PartialEq>(v: &[(usize, &T)], target: &mut [T], d: usize) {
221+
for (i, x) in v {
222+
target[d + i] = **x;
213223
}
214224
}
215225

216-
fn sort<T: PartialEq>(v: &[T], empty_val: T, row_length: usize) -> Vec<usize>
217-
where
218-
T: PartialEq<T>,
219-
{
226+
fn sort<T: PartialEq>(v: &[T], empty_val: T, row_length: usize) -> Vec<usize> {
220227
let mut o: Vec<usize> = (0..v.len() / row_length).collect();
221228
o.sort_by_key(|x| {
222229
v[(x * row_length)..((x + 1) * row_length)]

0 commit comments

Comments
 (0)