mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 03:42:17 +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
|
/// This ignores [Content::RecursionVar]s that occur recursively, because those are
|
||||||
/// already priced in and expected to occur.
|
/// already priced in and expected to occur.
|
||||||
pub fn occurs(&self, var: Variable) -> Result<(), (Variable, Vec<Variable>)> {
|
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(
|
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(
|
fn occurs(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
seen: &[Variable],
|
seen: &mut Vec<Variable>,
|
||||||
input_var: Variable,
|
input_var: Variable,
|
||||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||||
use self::Content::*;
|
use self::Content::*;
|
||||||
|
@ -3207,9 +3225,10 @@ fn occurs(
|
||||||
let root_var = subs.get_root_key_without_compacting(input_var);
|
let root_var = subs.get_root_key_without_compacting(input_var);
|
||||||
|
|
||||||
if seen.contains(&root_var) {
|
if seen.contains(&root_var) {
|
||||||
Err((root_var, vec![]))
|
Err((root_var, Vec::with_capacity(0)))
|
||||||
} else {
|
} else {
|
||||||
match subs.get_content_without_compacting(root_var) {
|
seen.push(root_var);
|
||||||
|
let result = (|| match subs.get_content_without_compacting(root_var) {
|
||||||
FlexVar(_)
|
FlexVar(_)
|
||||||
| RigidVar(_)
|
| RigidVar(_)
|
||||||
| FlexAbleVar(_, _)
|
| FlexAbleVar(_, _)
|
||||||
|
@ -3217,54 +3236,44 @@ fn occurs(
|
||||||
| RecursionVar { .. }
|
| RecursionVar { .. }
|
||||||
| Error => Ok(()),
|
| Error => Ok(()),
|
||||||
|
|
||||||
Structure(flat_type) => {
|
Structure(flat_type) => match flat_type {
|
||||||
let mut new_seen = seen.to_owned();
|
Apply(_, args) => {
|
||||||
|
short_circuit(subs, root_var, seen, subs.get_subs_slice(*args).iter())
|
||||||
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(()),
|
|
||||||
}
|
}
|
||||||
}
|
Func(arg_vars, closure_var, ret_var) => {
|
||||||
Alias(_, args, real_var, _) => {
|
let it = once(ret_var)
|
||||||
let mut new_seen = seen.to_owned();
|
.chain(once(closure_var))
|
||||||
new_seen.push(root_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() {
|
for var_index in args.into_iter() {
|
||||||
let var = subs[var_index];
|
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
|
// 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: _,
|
unspecialized: _,
|
||||||
ambient_function: _,
|
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
|
// unspecialized lambda vars excluded because they are not explicitly part of the
|
||||||
// type (they only matter after being resolved).
|
// 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(()),
|
RangedNumber(_range_vars) => Ok(()),
|
||||||
}
|
})();
|
||||||
|
seen.pop();
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3293,7 +3301,7 @@ fn occurs(
|
||||||
fn occurs_union<L: Label>(
|
fn occurs_union<L: Label>(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
root_var: Variable,
|
root_var: Variable,
|
||||||
seen: &[Variable],
|
seen: &mut Vec<Variable>,
|
||||||
tags: &UnionLabels<L>,
|
tags: &UnionLabels<L>,
|
||||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||||
for slice_index in tags.variables() {
|
for slice_index in tags.variables() {
|
||||||
|
@ -3310,7 +3318,7 @@ fn occurs_union<L: Label>(
|
||||||
fn short_circuit<'a, T>(
|
fn short_circuit<'a, T>(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
root_key: Variable,
|
root_key: Variable,
|
||||||
seen: &[Variable],
|
seen: &mut Vec<Variable>,
|
||||||
iter: T,
|
iter: T,
|
||||||
) -> Result<(), (Variable, Vec<Variable>)>
|
) -> Result<(), (Variable, Vec<Variable>)>
|
||||||
where
|
where
|
||||||
|
@ -3327,7 +3335,7 @@ where
|
||||||
fn short_circuit_help(
|
fn short_circuit_help(
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
root_key: Variable,
|
root_key: Variable,
|
||||||
seen: &[Variable],
|
seen: &mut Vec<Variable>,
|
||||||
var: Variable,
|
var: Variable,
|
||||||
) -> Result<(), (Variable, Vec<Variable>)> {
|
) -> Result<(), (Variable, Vec<Variable>)> {
|
||||||
if let Err((v, mut vec)) = occurs(subs, seen, var) {
|
if let Err((v, mut vec)) = occurs(subs, seen, var) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue