Skip to content

Commit 6bca7f0

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 d6077d2 commit 6bca7f0

File tree

1 file changed

+25
-17
lines changed

1 file changed

+25
-17
lines changed

src/lib.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,26 @@ 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+
tmp.extend(
187+
vec[s * row_length..(s + 1) * row_length]
188+
.iter()
189+
.enumerate()
190+
.filter(|(_, v)| **v != empty_val),
191+
);
192+
182193
let mut d = 0; // displacement value
183194
loop {
184-
if fits(slice, &r, d, empty_val) {
185-
apply(slice, &mut r, d, empty_val);
195+
if fits(tmp.as_slice(), &r, d, empty_val) {
196+
apply(tmp.as_slice(), &mut r, d);
186197
dv[*s] = d;
187198
break;
188199
} else {
@@ -196,27 +207,24 @@ fn compress<T: Clone + Copy + PartialEq>(
196207
(r, dv)
197208
}
198209

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] {
210+
/// `v` is an array of `(index, non-empty_val)` pairs.
211+
fn fits<T: PartialEq>(v: &[(usize, &T)], target: &[T], d: usize, empty_val: T) -> bool {
212+
for (i, x) in v {
213+
if target[d + i] != empty_val && target[d + i] != **x {
202214
return false;
203215
}
204216
}
205217
true
206218
}
207219

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-
}
220+
/// `v` is an array of `(index, non-empty_val)` pairs.
221+
fn apply<T: Copy + PartialEq>(v: &[(usize, &T)], target: &mut [T], d: usize) {
222+
for (i, x) in v {
223+
target[d + i] = **x;
213224
}
214225
}
215226

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

0 commit comments

Comments
 (0)