Use thread-local buffer for occurs check seen variables

This materially improves performance for programs that are
recursion-heavy (as most Roc programs will likely be).

```
$ hyperfine '/tmp/roc-old check examples/cli/cli-platform/Arg.roc' '/tmp/roc-new check examples/cli/cli-platform/Arg.roc' --warmup 10
Benchmark 1: /tmp/roc-old check examples/cli/cli-platform/Arg.roc
  Time (mean ± σ):      53.8 ms ±   1.3 ms    [User: 87.3 ms, System: 10.8 ms]
  Range (min … max):    52.2 ms …  60.4 ms    51 runs

Benchmark 2: /tmp/roc-new check examples/cli/cli-platform/Arg.roc
  Time (mean ± σ):      45.0 ms ±   1.6 ms    [User: 59.4 ms, System: 11.3 ms]
  Range (min … max):    42.6 ms …  49.8 ms    60 runs

Summary
  '/tmp/roc-new check examples/cli/cli-platform/Arg.roc' ran
    1.20 ± 0.05 times faster than '/tmp/roc-old check examples/cli/cli-platform/Arg.roc'
```

The time spent in `occurs` during checking for `Arg` drops from 50% to 23%.
This commit is contained in:
Ayaz Hafiz 2022-11-15 17:33:18 -06:00
parent c5afc5e237
commit 2d57aa2170
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58

View file

@ -1951,7 +1951,10 @@ impl Subs {
/// This ignores [Content::RecursionVar]s that occur recursively, because those are
/// already priced in and expected to occur.
pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> {
occurs(self, &[], var)
let mut scratchpad = take_occurs_scratchpad();
let result = occurs(self, &mut scratchpad, var);
put_occurs_scratchpad(scratchpad);
result
}
pub fn mark_tag_union_recursive(
@ -3196,9 +3199,24 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
}
}
std::thread_local! {
static SCRATCHPAD_FOR_OCCURS: RefCell<Option<Vec<Variable>>> = RefCell::new(Some(Vec::with_capacity(1024)));
}
fn take_occurs_scratchpad() -> Vec<Variable> {
SCRATCHPAD_FOR_OCCURS.with(|f| f.take().unwrap())
}
fn put_occurs_scratchpad(mut scratchpad: Vec<Variable>) {
SCRATCHPAD_FOR_OCCURS.with(|f| {
scratchpad.clear();
f.replace(Some(scratchpad));
});
}
fn occurs(
subs: &Subs,
seen: &[Variable],
seen: &mut Vec<Variable>,
input_var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> {
use self::Content::*;
@ -3207,9 +3225,10 @@ fn occurs(
let root_var = subs.get_root_key_without_compacting(input_var);
if seen.contains(&root_var) {
Err((root_var, vec![]))
Err((root_var, Vec::with_capacity(0)))
} else {
match subs.get_content_without_compacting(root_var) {
seen.push(root_var);
let result = (|| match subs.get_content_without_compacting(root_var) {
FlexVar(_)
| RigidVar(_)
| FlexAbleVar(_, _)
@ -3217,54 +3236,44 @@ fn occurs(
| RecursionVar { .. }
| Error => Ok(()),
Structure(flat_type) => {
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
match flat_type {
Apply(_, args) => {
short_circuit(subs, root_var, &new_seen, subs.get_subs_slice(*args).iter())
}
Func(arg_vars, closure_var, ret_var) => {
let it = once(ret_var)
.chain(once(closure_var))
.chain(subs.get_subs_slice(*arg_vars).iter());
short_circuit(subs, root_var, &new_seen, it)
}
Record(vars_by_field, ext_var) => {
let slice =
SubsSlice::new(vars_by_field.variables_start, vars_by_field.length);
let it = once(ext_var).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, &new_seen, it)
}
TagUnion(tags, ext_var) => {
occurs_union(subs, root_var, &new_seen, tags)?;
short_circuit_help(subs, root_var, &new_seen, *ext_var)
}
FunctionOrTagUnion(_, _, ext_var) => {
let it = once(ext_var);
short_circuit(subs, root_var, &new_seen, it)
}
RecursiveTagUnion(_, tags, ext_var) => {
occurs_union(subs, root_var, &new_seen, tags)?;
short_circuit_help(subs, root_var, &new_seen, *ext_var)
}
EmptyRecord | EmptyTagUnion => Ok(()),
Structure(flat_type) => match flat_type {
Apply(_, args) => {
short_circuit(subs, root_var, seen, subs.get_subs_slice(*args).iter())
}
}
Alias(_, args, real_var, _) => {
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
Func(arg_vars, closure_var, ret_var) => {
let it = once(ret_var)
.chain(once(closure_var))
.chain(subs.get_subs_slice(*arg_vars).iter());
short_circuit(subs, root_var, seen, it)
}
Record(vars_by_field, ext_var) => {
let slice = SubsSlice::new(vars_by_field.variables_start, vars_by_field.length);
let it = once(ext_var).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, seen, it)
}
TagUnion(tags, ext_var) => {
occurs_union(subs, root_var, seen, tags)?;
short_circuit_help(subs, root_var, seen, *ext_var)
}
FunctionOrTagUnion(_, _, ext_var) => {
let it = once(ext_var);
short_circuit(subs, root_var, seen, it)
}
RecursiveTagUnion(_, tags, ext_var) => {
occurs_union(subs, root_var, seen, tags)?;
short_circuit_help(subs, root_var, seen, *ext_var)
}
EmptyRecord | EmptyTagUnion => Ok(()),
},
Alias(_, args, real_var, _) => {
for var_index in args.into_iter() {
let var = subs[var_index];
if short_circuit_help(subs, root_var, &new_seen, var).is_err() {
if short_circuit_help(subs, root_var, seen, var).is_err() {
// Pay the cost and figure out what the actual recursion point is
return short_circuit_help(subs, root_var, &new_seen, *real_var);
return short_circuit_help(subs, root_var, seen, *real_var);
}
}
@ -3276,16 +3285,15 @@ fn occurs(
unspecialized: _,
ambient_function: _,
}) => {
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
// unspecialized lambda vars excluded because they are not explicitly part of the
// type (they only matter after being resolved).
occurs_union(subs, root_var, &new_seen, solved)
occurs_union(subs, root_var, seen, solved)
}
RangedNumber(_range_vars) => Ok(()),
}
})();
seen.pop();
result
}
}
@ -3293,7 +3301,7 @@ fn occurs(
fn occurs_union<L: Label>(
subs: &Subs,
root_var: Variable,
seen: &[Variable],
seen: &mut Vec<Variable>,
tags: &UnionLabels<L>,
) -> Result<(), (Variable, Vec<Variable>)> {
for slice_index in tags.variables() {
@ -3310,7 +3318,7 @@ fn occurs_union<L: Label>(
fn short_circuit<'a, T>(
subs: &Subs,
root_key: Variable,
seen: &[Variable],
seen: &mut Vec<Variable>,
iter: T,
) -> Result<(), (Variable, Vec<Variable>)>
where
@ -3327,7 +3335,7 @@ where
fn short_circuit_help(
subs: &Subs,
root_key: Variable,
seen: &[Variable],
seen: &mut Vec<Variable>,
var: Variable,
) -> Result<(), (Variable, Vec<Variable>)> {
if let Err((v, mut vec)) = occurs(subs, seen, var) {