mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
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:
parent
c5afc5e237
commit
2d57aa2170
1 changed files with 62 additions and 54 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue