Skip to content

Speed up default FromJSON/ToJSON instances #335

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

Merged
merged 1 commit into from
Jan 19, 2016
Merged

Speed up default FromJSON/ToJSON instances #335

merged 1 commit into from
Jan 19, 2016

Conversation

RyanGlScott
Copy link
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:

    class GToJSON f where
     gToJSON :: Options -> f a -> Value
     gToEncoding :: Options -> f a -> 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
Contributor

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

@RyanGlScott
Copy link
Member Author

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
Member Author

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
Copy link
Collaborator

bos commented Jan 19, 2016

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:master Jan 19, 2016
@phadej
Copy link
Collaborator

phadej commented Jan 20, 2016

👍

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

Successfully merging this pull request may close these issues.

4 participants