Skip to content

Binary encoding for externs #3841

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

Merged
merged 11 commits into from
Apr 12, 2020
Merged

Conversation

kritzcreek
Copy link
Member

I did some profiling on the ide load times, and everything was dominated by JSON parsing. So I tried deriving Store (a binary serialize lib) for all the data types necessary for ExternsFile and then using the binary encoding I get significant speedups. Here are the stats for pscid:

Before:

	   ide-bench.exe +RTS -N -p -RTS
	total time  =        3.49 secs   (12947 ticks @ 1000 us, 8 processors)
	total alloc = 8,460,187,856 bytes  (excludes profiling overheads)

After:

	   ide-bench.exe +RTS -N -p -RTS
	total time  =        0.50 secs   (1837 ticks @ 1000 us, 8 processors)
	total alloc = 2,664,745,016 bytes  (excludes profiling overheads)

I'm just opening this as a draft for now. Do we want to make the switch to a binary encoding? Do we want to use a different library? I've found binary, cereal and store so far and picked store because it had both TH as well as Generic deriving. It's possible we could see some speedups in incremental builds as well, but any sizeable project I have generates too many warnings from the changes in master to give good timings right now.

Probably don't want this to end up in the PR, but it makes it easier
to reproduce the speedups.
@garyb
Copy link
Member

garyb commented Apr 10, 2020

I have no problem with externs being binary personally, there's no real advantage to those files being human readable that I can think of.

@hdgarrood
Copy link
Contributor

Yeah, I always thought we would want to switch to binary at some point too.

@hdgarrood
Copy link
Contributor

I'm not terribly concerned about the choice of library; since the externs file format is internal we can always change it later if we find a different library is better suited.

case decodeStrict externsFile of
Nothing ->
case Store.decode externsFile of
Left _ ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to try to come up with a way of parsing the compiler version from an externs file which is independent of the rest of the encoding, so that we can preserve this error message (externs version mismatch). Do you think we could give ExternsFile a hand-written Store instance which puts the compiler version at the very beginning of the file, and then write a parser for it which just extracts the version and ignores the rest of the file?

@natefaubion
Copy link
Contributor

Related, I've created #3842 to track the bloat issues with externs.

@joneshf
Copy link
Member

joneshf commented Apr 11, 2020

Might also want to look into cborg. In some informal testing it ended up being about twice as fast as binary or store. Not sure if that was a fluke or I was using the others incorrectly. But it's a data point.

@kritzcreek
Copy link
Member Author

New findings!

Might also want to look into cborg

Thanks for that recommendation @joneshf. The cborg library and the stuff built on top of it are documented much better and I'd prefer to use an established binary format like cbor so if someone wants to hack together some tooling in, let's say, Rust they can do so.

The CBOR standard actually defines a bijection to JSON and https://hackage.haskell.org/package/cborg-json-0.2.2.0 implements it. So one thing we could do is continue using all our existing JSON instances (this also addresses @hdgarrood's concern about the version field) and put a little JSON <-> CBOR adapter in between reading and writing from the file system. Performance-wise this puts us somewhere in the middle between JSON and Store. It's 3 times faster than just using JSON, and two times slower than going for direct Store instances.

	   ide-bench.exe +RTS -N -p -RTS

	total time  =        0.96 secs   (3547 ticks @ 1000 us, 8 processors)
	total alloc = 5,209,416,280 bytes  (excludes profiling overheads)

This approach still has all the problems outlined by @natefaubion though, we're encoding the same filepath over and over, and even worse when we're reading the file back in we get no sharing.

Next I'll try to use https://hackage.haskell.org/package/serialise (typeclass based encoders/decoders on top of cborg) to get a fair comparison between cborg and store and if they're competitive I'll give interning the file paths a try.

@kritzcreek
Copy link
Member Author

Sad update :(

I derived instances for Serialise, but they don't seem to roundtrip. Whenever I try to decode a previously encoded .cbor file I get DeserialiseFailure 1 "unknown tag" as the only error output, which also gives me no way of figuring out where things went wrong.

@kritzcreek
Copy link
Member Author

New Update.... type-class based encoding + decoding failures as Exceptions tricked me (It tried to deserialise values of type Maybe ExternsFile). With the Serialise codecs I'm now getting comparable timings to the Store en/decoding.

	   ide-bench.exe +RTS -N -p -RTS

	total time  =        0.55 secs   (2020 ticks @ 1000 us, 8 processors)
	total alloc = 1,649,605,704 bytes  (excludes profiling overheads)

Since CBOR is a self-describing format I'll see if I can extract the version from it without trying to decode the whole ExternsFile.

@kritzcreek
Copy link
Member Author

kritzcreek commented Apr 12, 2020

Allright, I think this is in a reviewable state now.

In this PR I've changed the encoding for externs from JSON to CBOR. I'm using the serialise library to derive instances for all the relevant data types.

By doing this we're getting significant speedups in IDE startup time, but also in the normal compiler pipeline.
The performance measurements in this PR measure how long it takes to load the fully built pscid project and populate the IDE caches. It's dominated by the time it takes to deserialise the externs files, and drops from 3.5s to 0.55s. Memory allocations drop from 8,460,187,856 bytes to 1,649,605,704 bytes.
There are more low-hanging fruits to further speed this up (some of which have already been identified in this PR), but I think this is a good starting point.

Because we're serialising to CBOR, it's possible to extract information like the version number that was stored with the externs, without having to be able to decode the whole format. Not using JSON anymore doesn't remove any features we used to have, because of this property. We will break downstream users that relied on the JSON format, but as far as I can tell we've agreed that externs are a compiler internal format for which we are not giving stability guarantees in any way.

@kritzcreek kritzcreek marked this pull request as ready for review April 12, 2020 15:45
Copy link
Contributor

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! I'm very much looking forward to using this.

@kritzcreek
Copy link
Member Author

Thanks for the review @hdgarrood! I'll merge this and as mentioned earlier we can do more improvements in follow-up PRs.

@kritzcreek kritzcreek merged commit 8869eb3 into purescript:master Apr 12, 2020
@kritzcreek kritzcreek deleted the binary-externsfile branch April 12, 2020 18:09
justinwoo pushed a commit to justinwoo/purescript that referenced this pull request May 15, 2020
This commit changes the encoding for externs from JSON to CBOR. We're using the serialise library to derive instances for all the relevant data types.

By doing this we're getting significant speedups in IDE startup time, but also in the normal compiler pipeline.
The performance measurements in this PR measure how long it takes to load the fully built pscid project and populate the IDE caches. It's dominated by the time it takes to deserialise the externs files, and drops from 3.5s to 0.55s. Memory allocations drop from 8,460,187,856 bytes to 1,649,605,704 bytes.
@hdgarrood hdgarrood mentioned this pull request May 23, 2020
hdgarrood pushed a commit that referenced this pull request May 23, 2020
This commit changes the encoding for externs from JSON to CBOR. We're using the serialise library to derive instances for all the relevant data types.

By doing this we're getting significant speedups in IDE startup time, but also in the normal compiler pipeline.
The performance measurements in this PR measure how long it takes to load the fully built pscid project and populate the IDE caches. It's dominated by the time it takes to deserialise the externs files, and drops from 3.5s to 0.55s. Memory allocations drop from 8,460,187,856 bytes to 1,649,605,704 bytes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants