Skip to content

Speed up default FromJSON/ToJSON instances - #335

Merged
bos merged 1 commit into
haskell:masterfrom
RyanGlScott:master
Jan 19, 2016
Merged

Speed up default FromJSON/ToJSON instances#335
bos merged 1 commit into
haskell:masterfrom
RyanGlScott:master

Conversation

@RyanGlScott

Copy link
Copy Markdown
Member

Fixes#296 and #309 (I believe).

The DefaultInstances-based mechanism for deriving FromJSON and ToJSON instances via GHC generics currently consumes way more memory than it should. I believe this is the result of two things:

  1. GHC Trac #9630. It was discovered that having generic classes that have more than one method such as:

    classGToJSONfwheregToJSON::Options->fa->ValuegToEncoding::Options->fa->Encoding

    hurt the optimizer badly. To compensate for this bug, I split GToEncoding off from GToJSON (as well as the many internal typeclasses that GToJSON uses).

  2. Data.Aeson.Types.Generic puts INLINE pragmas on all generic class instance methods. This results in an explosion of inlined code, to the point that pandoc-types takes ~7 GB of memory to compile. In my experience, inlining generic methods has turned out to be a bad idea, so I've removed the INLINE pragmas.

To test out the improvments, I did a very fast-and-loose profiling of the time and memory it takes to compile pandoc-types (a package known to be affected badly by the aeson-0.10 compilation regressions). I did these tests on a 64-bit Linux laptop with 4 GB of RAM. Here are the results:

  • pandoc-types (vanilla aeson-0.10):
$ /usr/bin/time -v cabal install pandoc-types
Resolving dependencies...
Downloading pandoc-types-1.16.0.1...
Configuring pandoc-types-1.16.0.1...
Building pandoc-types-1.16.0.1...
Installed pandoc-types-1.16.0.1
Command being timed: "cabal install pandoc-types"
User time (seconds): 251.96
System time (seconds): 5.90
Percent of CPU this job got: 41%
Elapsed (wall clock) time (h:mm:ss or m:ss): 10:20.85
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3048536
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 54055
Minor (reclaiming a frame) page faults: 1332223
Voluntary context switches: 61491
Involuntary context switches: 15193
Swaps: 0
File system inputs: 3456696
File system outputs: 123552
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
  • pandoc-types (aeson-0.10 with the changes in this pull request):
$ /usr/bin/time -v cabal install pandoc-types
Resolving dependencies...
Configuring pandoc-types-1.16.0.1...
Building pandoc-types-1.16.0.1...
Installed pandoc-types-1.16.0.1
Command being timed: "cabal install pandoc-types"
User time (seconds): 39.66
System time (seconds): 0.89
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:41.25
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 501804
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 55
Minor (reclaiming a frame) page faults: 324617
Voluntary context switches: 2115
Involuntary context switches: 1401
Swaps: 0
File system inputs: 14520
File system outputs: 87472
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0

The total wall time went from 10 minutes to under a minute, and it went from using 3 GB of RAM (and thrashing my laptop mercilessly) to about 500 MB of RAM.

@gregwebs

Copy link
Copy Markdown
Contributor

nice! too much inlining strikes again. What cabal version bump is needed here?

@RyanGlScott

Copy link
Copy Markdown
MemberAuthor

I'm not sure. This does make an API change (albeit a small one) in that GToJSON now only has one method (gToJSON), and gToEncoding was moved to the new GToEncoding class. I suppose it's up to @bos / @basvandijk whether they consider those typeclasses "internal" or not (and thus whether this warrants a minor or major version bump).

@RyanGlScott

Copy link
Copy Markdown
MemberAuthor

For completeness's sake, I went ahead and ran the most relevant benchmark I could find in the aeson repo, which is AesonCompareAutoInstances. After updating it (it has some outdated aeson/criterion utilities), I ran it with both vanilla aeson-0.10 and aeson-0.10 with my changes:

  • aeson-0.10 (no changes):
benchmarking D/toJSON/generic
time 20.97 μs (20.71 μs .. 21.32 μs)
0.999 R² (0.998 R² .. 1.000 R²)
mean 20.87 μs (20.76 μs .. 21.04 μs)
std dev 459.8 ns (299.0 ns .. 681.2 ns)
variance introduced by outliers: 21% (moderately inflated)
benchmarking D/fromJSON/generic
time 1.744 μs (1.712 μs .. 1.801 μs)
0.993 R² (0.984 R² .. 1.000 R²)
mean 1.758 μs (1.725 μs .. 1.836 μs)
std dev 147.7 ns (79.95 ns .. 252.8 ns)
variance introduced by outliers: 84% (severely inflated)
benchmarking BigRecord/toJSON/generic
time 15.36 μs (15.23 μs .. 15.50 μs)
0.999 R² (0.999 R² .. 1.000 R²)
mean 15.34 μs (15.25 μs .. 15.56 μs)
std dev 460.9 ns (188.8 ns .. 825.6 ns)
variance introduced by outliers: 34% (moderately inflated)
benchmarking BigRecord/fromJSON/generic
time 15.89 μs (15.75 μs .. 16.12 μs)
0.995 R² (0.989 R² .. 0.999 R²)
mean 16.39 μs (15.98 μs .. 17.37 μs)
std dev 1.927 μs (1.106 μs .. 2.988 μs)
variance introduced by outliers: 89% (severely inflated)
benchmarking BigProduct/toJSON/generic
time 5.091 μs (5.016 μs .. 5.182 μs)
0.996 R² (0.991 R² .. 0.999 R²)
mean 5.139 μs (5.054 μs .. 5.313 μs)
std dev 391.7 ns (207.9 ns .. 654.9 ns)
variance introduced by outliers: 80% (severely inflated)
benchmarking BigProduct/fromJSON/generic
time 9.104 μs (8.999 μs .. 9.243 μs)
0.998 R² (0.996 R² .. 0.999 R²)
mean 9.171 μs (9.060 μs .. 9.342 μs)
std dev 443.7 ns (324.4 ns .. 590.7 ns)
variance introduced by outliers: 59% (severely inflated)
benchmarking BigSum/toJSON/generic
time 281.9 ns (277.7 ns .. 287.5 ns)
0.997 R² (0.994 R² .. 0.999 R²)
mean 287.9 ns (281.8 ns .. 299.0 ns)
std dev 25.88 ns (17.31 ns .. 35.75 ns)
variance introduced by outliers: 88% (severely inflated)
benchmarking BigSum/fromJSON/generic
time 4.977 μs (4.769 μs .. 5.276 μs)
0.991 R² (0.982 R² .. 0.999 R²)
mean 4.880 μs (4.810 μs .. 5.035 μs)
std dev 317.8 ns (176.9 ns .. 500.9 ns)
variance introduced by outliers: 74% (severely inflated)
  • aeson-0.10 (my changes):
benchmarking D/toJSON/generic
time 20.97 μs (20.60 μs .. 21.46 μs)
0.998 R² (0.996 R² .. 1.000 R²)
mean 20.99 μs (20.81 μs .. 21.30 μs)
std dev 763.4 ns (503.0 ns .. 1.047 μs)
variance introduced by outliers: 42% (moderately inflated)
benchmarking D/fromJSON/generic
time 1.744 μs (1.738 μs .. 1.751 μs)
0.999 R² (0.997 R² .. 1.000 R²)
mean 1.811 μs (1.769 μs .. 1.898 μs)
std dev 200.7 ns (120.0 ns .. 361.6 ns)
variance introduced by outliers: 90% (severely inflated)
benchmarking BigRecord/toJSON/generic
time 14.46 μs (14.14 μs .. 14.84 μs)
0.997 R² (0.996 R² .. 1.000 R²)
mean 14.25 μs (14.14 μs .. 14.44 μs)
std dev 473.4 ns (323.9 ns .. 734.1 ns)
variance introduced by outliers: 39% (moderately inflated)
benchmarking BigRecord/fromJSON/generic
time 19.25 μs (18.82 μs .. 19.81 μs)
0.996 R² (0.994 R² .. 0.999 R²)
mean 19.37 μs (19.06 μs .. 20.06 μs)
std dev 1.516 μs (964.6 ns .. 2.433 μs)
variance introduced by outliers: 78% (severely inflated)
benchmarking BigProduct/toJSON/generic
time 5.496 μs (5.458 μs .. 5.564 μs)
0.999 R² (0.998 R² .. 1.000 R²)
mean 5.551 μs (5.497 μs .. 5.637 μs)
std dev 222.7 ns (153.2 ns .. 285.2 ns)
variance introduced by outliers: 51% (severely inflated)
benchmarking BigProduct/fromJSON/generic
time 8.805 μs (8.690 μs .. 8.935 μs)
0.999 R² (0.997 R² .. 1.000 R²)
mean 8.785 μs (8.720 μs .. 8.991 μs)
std dev 352.2 ns (143.3 ns .. 734.0 ns)
variance introduced by outliers: 50% (moderately inflated)
benchmarking BigSum/toJSON/generic
time 263.0 ns (254.1 ns .. 276.1 ns)
0.992 R² (0.983 R² .. 0.999 R²)
mean 257.6 ns (254.2 ns .. 264.7 ns)
std dev 16.05 ns (8.483 ns .. 28.06 ns)
variance introduced by outliers: 78% (severely inflated)
benchmarking BigSum/fromJSON/generic
time 4.662 μs (4.575 μs .. 4.782 μs)
0.994 R² (0.988 R² .. 0.999 R²)
mean 4.656 μs (4.583 μs .. 4.815 μs)
std dev 328.2 ns (166.2 ns .. 539.5 ns)
variance introduced by outliers: 77% (severely inflated)

@bos

bos commented Jan 19, 2016

Copy link
Copy Markdown
Collaborator

Really nice work, @RyanGlScott – thank you! I'm applying this right now.

bos added a commit that referenced this pull request Jan 19, 2016
Speed up default FromJSON/ToJSON instances
@bos
bos merged commit ac6b303 into haskell:masterJan 19, 2016
@phadej

Copy link
Copy Markdown
Collaborator

👍

Sign up for freeto 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.

4 participants

@RyanGlScott@gregwebs@bos@phadej