Skip to content

Arithmetic operations with inferred variables take quadratic time to type-check #25916

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
huonw opened this issue May 31, 2015 · 5 comments · Fixed by #31680
Closed

Arithmetic operations with inferred variables take quadratic time to type-check #25916

huonw opened this issue May 31, 2015 · 5 comments · Fixed by #31680
Labels
I-compiletime Issue: Problems and improvements with respect to compile times. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@huonw
Copy link
Member

huonw commented May 31, 2015

This takes ~2 seconds to type check (according to -Z time-passes):

fn main() {
    macro_rules! f {
        () => { 0 + 0 }
    }
    // 16 per line
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();

    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();

    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();

    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
    f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();f!();
}

Doubling (respectively, halving) the number of f!() invocations multiplies (resp. divides) the time by 4.

It applies to arithmetic in consts/statics too which is, I imagine, where this will occur most often in practice (it's how I found it).

Observations:

  • for +, -, * and /, annotating either side (e.g. 0 + 0u8) or both sides makes it take only 0.03s and behave linearly (AFAICT)
  • for << and >>:
    • annotating the LHS (0u8 >> 0) reduces the time to ~1.5s,
    • annotating the RHS (0 >> 0u8) reduces it to ~1.1s but both are still quadratic,
    • annotating both takes it to 0.04s and linear
  • it seems to only be dependent on the number of operations in a single function/item, separating the 4 big chunks above across 4 functions reduces the time to ~0.5s.
  • there's no significant difference between using the macro or writing the operations inline (I only use f! to allow easier experimentation)

cc @rust-lang/compiler

@huonw huonw added I-compiletime Issue: Problems and improvements with respect to compile times. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 31, 2015
@eddyb
Copy link
Member

eddyb commented May 31, 2015

This must be what's making my old macro-abusing inflate implementation take almost 300 seconds in typeck.
It almost sounds like type-checking a function is quadratic over the number of inference variables.

Having ran callgrind on the sample and also half of it, I'm seeing the following trace:

Function Calls (N=128) Calls (N=256) Calls (N)
check::op::check_binop 128 256 N
check::FnCtxt::resolve_type_vars_if_possible 512 1024 4N
check::FnCtxt::select_obligations_where_possible 512 + 12 1024 + 12 4N + 12
traits::FulfillmentContext::select_where_possible 512 + 27 1024 + 27 4N + 27
traits::FulfillmentContext::select 1024 + 155 2048 + 183 8N + M
infer::InferCtxt::commit_if_ok 83072 329984 5N² + 9N

An unrefined conclusion is that either check_binop shouldn't call fcx.resolve_type_vars_if_possible() or FulfillmentContext::select should be constant-time.

eddyb added a commit to eddyb/rust that referenced this issue May 31, 2015
The `HashMap` and `HashSet` iterators use `RawTable::first_bucket_raw` which is generic and will get inlined cross-crate.
However, `first_bucket_raw` calls `calculate_offsets` and the call doesn't get inlined, despite being a simple function.
This missing `#[inline]` results in `hash_table::calculate_offsets` showing up at the top of a callgrind profile with 3 million calls (for the testcase in rust-lang#25916).
@bluss
Copy link
Member

bluss commented May 31, 2015

Is this the same issue as #23977?

@eddyb
Copy link
Member

eddyb commented May 31, 2015

@nikomatsakis I'm not too familiar with fulfill and infer, but couldn't select_where_possible track the latest InferCtxt snapshot it processed and look at the new information there?

bors added a commit that referenced this issue May 31, 2015
The `HashMap` and `HashSet` iterators use `RawTable::first_bucket_raw` which is generic and will get inlined cross-crate.
However, `first_bucket_raw` calls `calculate_offsets` and the call doesn't get inlined, despite being a simple function.
This missing `#[inline]` results in `hash_table::calculate_offsets` showing up at the top of a callgrind profile with 3 million calls (for the testcase in #25916).
@arielb1
Copy link
Contributor

arielb1 commented May 31, 2015

@eddyb

We have select_new_obligations exactly for this reason. It doesn't work for some reason tho.

@arielb1
Copy link
Contributor

arielb1 commented Jun 2, 2015

This is the same issue as
#25069
#23977

This is a different issue from #22204 (I think I will fix that one separately).

arielb1 pushed a commit that referenced this issue Aug 9, 2015
This speeds up rustc on #25916 from 1.36Â0.022s to 1.326Â0.025s
arielb1 pushed a commit to arielb1/rust that referenced this issue Aug 13, 2015
This speeds up rustc on rust-lang#25916 from 1.36±0.022s to 1.326±0.025s

Tests pass locally (even on 32-bit :-)
bors added a commit that referenced this issue Aug 14, 2015
This speeds up rustc on #25916 from 1.36±0.022s to 1.326±0.025s

Tests pass locally (even on 32-bit :-)

r? @gankro
arielb1 pushed a commit to arielb1/rust that referenced this issue Jan 31, 2016
arielb1 pushed a commit that referenced this issue Feb 15, 2016
this improves typeck performance by 5% (LLVM times are still huge).

Basically fixes #25916 (still O(n^2), but the example takes <1s to
compile).
bors added a commit that referenced this issue Feb 16, 2016
this improves typeck performance by 5% (LLVM times are still huge).

Basically fixes #25916 (still O(n^2), but the example takes <1s to
compile).

r? @nikomatsakis
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
I-compiletime Issue: Problems and improvements with respect to compile times. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants