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

Extremely long compile times #21

Closed
hawkw opened this issue Mar 21, 2015 · 22 comments
Closed

Extremely long compile times #21

hawkw opened this issue Mar 21, 2015 · 22 comments
Labels

Comments

@hawkw
Copy link
Contributor

hawkw commented Mar 21, 2015

I know this issue is a little vague, so please bear with me. As I've started using parser-combinators, I've noticed that the compile times for my project have sharply increased; more than I would expect from the amount of code I've written. cargo build now takes 8 to 11 minutes to complete. This is on a fairly new machine, too - I have a MacBook Pro with a 2.5GHz quad-core i7, and I've generally seen very good performance from cargo/rustc.

I guess I'm just curious to know what's causing these very long compile times. Is this to be expected when using this library, am I misusing it in some way that's confusing rustc, or is something else wrong? Of course, it's likely difficult to determine exactly what's going on, but I'd welcome any additional information.

@Marwes
Copy link
Owner

Marwes commented Mar 21, 2015

I thought I had an issue open for this but it appears I had forgotten open one.

rustc seems to have some issues with deeply nested types + associated types (see rust-lang/rust#21231).

To workaround it until it gets fixed you have to write smaller parsers, ie factor out parts into freestanding functions instead of storing them in local variables.

Example at: https://github.com/Marwes/parser-combinators/blob/master/benches/json.rs#L110-L128

(It might be possible to specialize the parsers directly as well, say

fn expr(input: State<&str>) -> ParseResult<Expr, &str>

instead of

fn expr<I: Stream>(input: State<I>) -> ParseResult<I, &str>

)

@hawkw
Copy link
Contributor Author

hawkw commented Mar 21, 2015

@Marwes Excellent, thank you very much for the quick advice, I really appreciate that. I'll try refactoring some of my code and let you know if I'm still having problems.

@hawkw
Copy link
Contributor Author

hawkw commented Mar 21, 2015

Okay, I did some refactoring (hawkw/seax@96af0b7) and compile times have reduced significantly. My laptop no longer smells like it's melting, and maybe I can even turn lint-on-save back on. Thanks so much.

hawkw added a commit to hawkw/seax that referenced this issue Mar 22, 2015
Specialized the parsers to parse only over `&str`s, rather than
`Stream<Item=char>`. I’d rather the parsers be able to operate over any
stream of characters, but this improves the compile time somewhat (see
Marwes/combine#21).

Once the rustc issues are resolved, it should be trivial to
re-generalize the parsers.
@Marwes
Copy link
Owner

Marwes commented May 1, 2015

I happened upon a workaround to half of this problem, namely the part where functions need to be specialized.

Say you have a bunch of parser functions:

fn parse1<I>(input: State<I>) -> ParseResult<i32, I> where I: Stream { ... }
fn parse2<I>(input: State<I>) -> ParseResult<i32, I> where I: Stream  { ... }
fn parse3<I>(input: State<I>) -> ParseResult<i32, I> where I: Stream  { ... }

If you move all of these functions into an impl block for a type you can get the same compile times as with the specialized version like this:

struct P<I>(PhantomData<fn (I) -> I>);
impl <I> P<I>
    where I: Stream {
    fn parse1(input: State<I>) -> ParseResult<i32, I> { ... }
    fn parse2(input: State<I>) -> ParseResult<i32, I> { ... }
    fn parse3(input: State<I>) -> ParseResult<i32, I> { ... }
}

In addition to this, when these functions refer to each other they need to be specialized, i.e write P::<I>::parse1 and not P::parse1. The second form unfortunately triggers the slow behavior so make sure that all functions are specialized if it is still slow.

@hawkw
Copy link
Contributor Author

hawkw commented May 1, 2015

Interesting! I don't think that will help my current situation particularly (all of my parsers are specialized and that's not particularly a problem; compile times are still slow since they're still somewhat complex), but it's good to know!

@tari
Copy link

tari commented May 2, 2015

rust-lang/rust#22204 might be related too.

@Marwes
Copy link
Owner

Marwes commented May 18, 2015

rust-lang/rust#20304 also seems to be an issue and reducing the nesting of types does not seem to workaround that problem either. After using the above techniques on https://github.com/Marwes/parser-combinators-language the trans phase still takes a very long time with about half the total time spent just normalizing associated types =/

@Marwes
Copy link
Owner

Marwes commented Jul 17, 2015

I guess it should also be mentioned that moving the parser implementation to a separate crate which only expose specialized parser functions can be used to avoid long compile times on every recompile as rustc can just link the the already compiled code in that case, avoiding a full recompilation.

@Ralle
Copy link

Ralle commented Jul 18, 2015

I have done things recommended here, but I still get a compile time > 1 minute. Do you know what I can do to improve on things?
https://gist.github.com/Ralle/ca7533ed8faa0bd788d8

@Marwes
Copy link
Owner

Marwes commented Jul 18, 2015

@Ralle Either wrap all your functions in a type as I suggested above or specialize all your I parameters to &str. If you are using a stable compiler you might want to change to nightly (even if you don't use any unstable features) since there are some significant performance improvements.

If you still find the compilation times move the parser to its own crate and you will at least avoid the compile times when you do not modify the parser.

@Marwes
Copy link
Owner

Marwes commented Jul 18, 2015

I have been meaning to add the project that I use combine and combine-language in pretty soon though I have not had the time to get it into proper shape yet. That should display what I have done to make it work with a large parser in a compiler.

@hawkw
Copy link
Contributor Author

hawkw commented Jul 18, 2015

I would love to see that whenever it's ready to be shown to the public; I've been thinking about trying to refactor my Scheme parser some, and I'd like to see how you're using the library in your own code.

@Ralle
Copy link

Ralle commented Jul 19, 2015

Thank you for all your help so far.

I upgraded to the latest Rust and did the struct thing that you showed here. Still 1m 20s, I shaved off 20s. Any more suggestions?
https://gist.github.com/Ralle/6eb3234c665a4dde8940

@Marwes
Copy link
Owner

Marwes commented Jul 19, 2015

Reducing how many levels you nest parsers can reduce the typechecking times a bit but unfortunately the compile times will always be quite long compared to other rust code.

I did some modifications to your gist, changing or to choice and using tuple parsers instead of chaining skip/with. I got it down about 10-15 more seconds, shrinking a few of the other parsers should maybe shave of 10 more seconds. After that most time will be spent in translation which I think we need to wait for rust-lang/rust#20304 to see some lower times.

The best thing really, is to have the parser in separate crate as mentioned above. In the project I mentioned above it takes about 2m30s to compile (4m30s really since I specialize it for two different outputs) (EDIT: I do something weird with my parser tests since compiling the rest of the compiler, including the parser but not the parser tests, takes 3m11s) it but by keeping it in a separate crate I don't actually notice it since it rarely needs a full recompilation. I seem to remember it being shorter though so maybe there has been a regression in a recent nightly, might need to investigate.

Gist

EDIT: When I compile without needing to recompile the parser a compilation takes just 20s.

@hawkw
Copy link
Contributor Author

hawkw commented Jul 19, 2015

Reducing how many levels you nest parsers can reduce the typechecking times a bit

Which is a shame, because of how one is generally kind of intended to use parser combinators. :)

@Marwes
Copy link
Owner

Marwes commented Jul 19, 2015

Its a shame that its necessary to reduce some nesting of the types but I'd say its generally a good idea to create many small parsers anyway. If really need to create a large parser you either have to wrap it in a closure (parser(|input| large_parser.parse_state(input)) or cast the parser to a trait object let parser: Parser<Input=I, Output=O> = &mut large_parser. Its not ideal but at least it works in the interim.

@Ralle
Copy link

Ralle commented Jul 20, 2015

Your suggestions helped me a lot. Thank you!

@Marwes
Copy link
Owner

Marwes commented Aug 2, 2015

I finally got the compiler into bit better shape and so I moved it to github. You can find it here. The parser is found in the parser folder for anyone looking for how I use combine. The full compile times are indeed long but as long as the parser is not modified it is as fast as any other rust program.

@Marwes Marwes added the Slow label Oct 21, 2015
@Marwes
Copy link
Owner

Marwes commented Jan 31, 2016

There seems to be a regression in typechecking in recent nightlies as well as the 1.7.0 beta. If you are using a nightly version I really recommend using one before these regressions as compiletimes are really bad regardless of the workarounds. My 6 year old laptop takes 35 minutes when compiling tests (for combine) vs 2 minutes before the regression.

Rust issue: rust-lang/rust#31157

@pepyakin
Copy link

pepyakin commented Jun 6, 2016

It seems to be better on the latest nightly-2016-06-06. I see 4х improvement in compile time.

@Marwes
Copy link
Owner

Marwes commented Jun 7, 2016

@pepyakin Thanks for reminding me to post here! Yeah, with rust-lang/rust#33816 merged I recommend using a nightly after 2016-06-05 until the release trains catch up.

@Marwes
Copy link
Owner

Marwes commented Aug 21, 2016

Closing this as with the release of rust 1.11 there is no longer an exponential compile time problem (compiling can still be a little slow due the the amount of types created but its no longer critical issue that has to be worked around).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants