Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Exponential trait selection when compiling a crate using combine 4 #68666

Open
Marwes opened this issue Jan 30, 2020 · 11 comments
Open

Exponential trait selection when compiling a crate using combine 4 #68666

Marwes opened this issue Jan 30, 2020 · 11 comments
Labels
A-trait-system Area: Trait system C-bug Category: This is a bug. 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

@Marwes
Copy link
Contributor

Marwes commented Jan 30, 2020

Originally reported in Marwes/combine#284 . It appears that the changes made between version 3 and 4 in https://github.com/Marwes/combine . Made trait selection exponential in some cases. The main change that I would suspect causing this is that combine-3 had Input as an associated type whereas combine-4 uses a type parameter.

The following minimized repo reproduces the slowdown, removing a few arguments from the choice! macro makes it compile quickly https://github.com/Marwes/combine-slow-compile . The commit before master contains a version with combine-3 which compiles instantaneously (after dependencies are compiled). (diff Marwes/combine-slow-compile@21cf38a)

Screenshot from 2020-01-30 10-51-55

@Marwes Marwes changed the title Exponential trait selection when compiling combine 4 Exponential trait selection when compiling a crate using combine 4 Jan 30, 2020
@jonas-schievink jonas-schievink added A-trait-system Area: Trait system C-bug Category: This is a bug. E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example 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 Jan 30, 2020
@sdroege
Copy link
Contributor

sdroege commented Jan 30, 2020

The E-needs-mcve label seems wrong, there's a minimal testcase linked in the initial description :)

@sdroege
Copy link
Contributor

sdroege commented Jan 30, 2020

This is btw the same function as in #67454, just that with combine 3 this works fine again now while with combine 4 it exposes the same behaviour

@jonas-schievink
Copy link
Contributor

I tagged it needs-MCVE because the reproduction above still pulls in 2 dependencies. It would be helpful to get it down to a single file.

@Marwes
Copy link
Contributor Author

Marwes commented Jan 30, 2020

Minimized, add more token parsers to get more of a slowdown.

use std::marker::PhantomData;

pub trait Parser<Input> {
    type Output;

    fn or<P2>(self, p: P2) -> Or<Self, P2>
    where
        Self: Sized,
        P2: Parser<Input, Output = Self::Output>,
    {
        Or(self, p)
    }
}

#[macro_export]
macro_rules! choice {
    ($first : expr) => {
        $first
    };
    ($first : expr, $($rest : expr),+) => {
        $first.or(choice!($($rest),+))
    }
}

pub struct Or<P1, P2>(P1, P2);
impl<Input, P1, P2> Parser<Input> for Or<P1, P2>
where
    P1: Parser<Input, Output = P2::Output>,
    P2: Parser<Input>,
{
    type Output = P2::Output;
}

pub struct P<I, O>(PhantomData<fn(I) -> O>);
impl<Input, O> Parser<Input> for P<Input, O> {
    type Output = O;
}

pub fn token<I>(_: u8) -> impl Parser<I, Output = u8> {
    P(PhantomData)
}

fn mcc_payload_item<I>() -> impl Parser<I, Output = u8> {
    choice!(
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G'),
        token(b'G')
    )
}

fn main() {}

@nnethercote
Copy link
Contributor

This was fixed by #67471, which reverted the regressing code from #66405.

@Marwes
Copy link
Contributor Author

Marwes commented Feb 5, 2020

No, this isn't fixed by #67471 . This is a separate issue (even if the reproductions are really similar).

@jonas-schievink jonas-schievink removed the E-needs-mcve Call for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example label Feb 5, 2020
@nnethercote
Copy link
Contributor

Ok, sorry for the confusion. Just to clarify: there is no rustc regression involved here, right?

@Marwes
Copy link
Contributor Author

Marwes commented Feb 5, 2020

No, i looked back a few versions and it was always slow.

@nnethercote
Copy link
Contributor

nnethercote commented Feb 13, 2020

I increased the size of the benchmark from 16 tokens to 19 and did some profiling.

register_obligation_at is called 3.4M times. It accounts for most of the runtime.

The length of self.nodes gets up to 1.4M.

Worst of all, the length of at least one node.dependents gets up to 131k, which is extraordinary. This results in quadratic behaviour, because of this code, because contains does a linear search:

    if !node.dependents.contains(&parent_index) {
        node.dependents.push(parent_index);
    }

I.e. the length of this node.dependents grows from 0 to 131k one at a time, with a failing linear search on every push.

I printed out the predicates being added, there are a lot (786k) of these ones:

Binder(TraitPredicate(<impl Parser<_> as std::marker::Sized>))

I don't really understand the predicate stuff, but maybe @nikomatsakis has some thoughts about this?

@Marwes
Copy link
Contributor Author

Marwes commented Feb 16, 2020

@nnethercote Got a big PR in the works which rewrites most of ObligationForest. It doesn't affect this particular issue (sadly) but it is likely to collide with any changes to fix this issue.

#69218

@Marwes
Copy link
Contributor Author

Marwes commented Mar 17, 2020

Seeing what looks like this issue in futures heavy code as well (in two different projects). Root issue is that there are just too many obligations generated, nothing can be done to ObligationForest itself to speed this up, I think. Selection just need to generate fewer obligations, or at least they must be memoized better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-trait-system Area: Trait system C-bug Category: This is a bug. 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

No branches or pull requests

4 participants