deriving-aeson icon indicating copy to clipboard operation
deriving-aeson copied to clipboard

Over 10 times slower compilation with aeson 2.x

Open infinisil opened this issue 4 years ago • 4 comments

It looks like this library got considerably slower with aeson 2.x in comparison to 1.x. According to my measurements, each additional value in an enum now takes ~0.6 seconds to compile, whereas before it was ~0.05 seconds, a slowdown of about 12 times.

The test case I'm using is

{-# LANGUAGE DerivingVia, DataKinds, DeriveGeneric #-}
module Module where

import Data.Aeson
import Deriving.Aeson

data SomeType
  = SomeValue0
  | SomeValue1
  | SomeValue2
  | SomeValue3
  | SomeValue4
  | SomeValue5
  | SomeValue6
  | SomeValue7
  | SomeValue8
  | SomeValue9
  | SomeValue10
  | SomeValue11
  | SomeValue12
  | SomeValue13
  | SomeValue14
  | SomeValue15
  | SomeValue16
  | SomeValue17
  | SomeValue18
  | SomeValue19
  | SomeValue20
  | SomeValue21
  | SomeValue22
  | SomeValue23
  | SomeValue24
  | SomeValue25
  | SomeValue26
  | SomeValue27
  | SomeValue28
  | SomeValue29
  deriving Generic
  deriving ToJSON via CustomJSON '[] SomeType

Here's a full reproducer of this problem: https://github.com/tweag/webauthn/tree/deriving-aeson-slow-repro

infinisil avatar Jan 04 '22 20:01 infinisil

I observed a significant slowdown too. Do you see the slowness when you use genericToJSON directly?

fumieval avatar Jan 05 '22 06:01 fumieval

No, it only occurs with the custom deriving. Deriving with standard aeson takes the same amount of time for 1.x and 2.x.

infinisil avatar Jan 05 '22 18:01 infinisil

I was able to inline deriving-aeson and minimizing it, ending up with the same slowdown but much simpler code:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE UndecidableInstances #-}

module Module where

import Data.Coerce (coerce)
import GHC.Generics
import Data.Aeson

data SomeType = SomeValue0 | SomeValue1 | SomeValue2 | SomeValue3 | SomeValue4 | SomeValue5 | SomeValue6 | SomeValue7 | SomeValue8 | SomeValue9 | SomeValue10 | SomeValue11 | SomeValue12 | SomeValue13 | SomeValue14 | SomeValue15 | SomeValue16 | SomeValue17 | SomeValue18 | SomeValue19 | SomeValue20 | SomeValue21 | SomeValue22 | SomeValue23 | SomeValue24 | SomeValue25 | SomeValue26 | SomeValue27 | SomeValue28 | SomeValue29 deriving Generic
  deriving FromJSON via CustomJSON

newtype CustomJSON = CustomJSON SomeType

instance GFromJSON Zero (Rep SomeType) => FromJSON CustomJSON where
  parseJSON value = CustomJSON <$> genericParseJSON defaultOptions value

But notably, the slowdown gets reduced to only about 2-fold when the instance constraint is removed, which also removes the need for UndecidableInstances:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE DeriveGeneric #-}

module Module where

import Data.Coerce (coerce)
import GHC.Generics
import Data.Aeson

data SomeType = SomeValue0 | SomeValue1 | SomeValue2 | SomeValue3 | SomeValue4 | SomeValue5 | SomeValue6 | SomeValue7 | SomeValue8 | SomeValue9 | SomeValue10 | SomeValue11 | SomeValue12 | SomeValue13 | SomeValue14 | SomeValue15 | SomeValue16 | SomeValue17 | SomeValue18 | SomeValue19 | SomeValue20 | SomeValue21 | SomeValue22 | SomeValue23 | SomeValue24 | SomeValue25 | SomeValue26 | SomeValue27 | SomeValue28 | SomeValue29 deriving Generic
  deriving FromJSON via CustomJSON

newtype CustomJSON = CustomJSON SomeType

instance FromJSON CustomJSON where
  parseJSON value = CustomJSON <$> genericParseJSON defaultOptions value

So this probably has something to do with aeson's generics classes/instances

infinisil avatar Jan 05 '22 19:01 infinisil

Thanks, I was able to reproduce it too. Interesting... I wonder if there's a workaround

fumieval avatar Jan 11 '22 04:01 fumieval