{-# language BangPatterns #-}
{-# language PackageImports #-}
{-# language MagicHash #-}
{-# language ScopedTypeVariables #-}
import Gauge (bgroup,bench,whnf)
import Gauge.Main (defaultMain)
import Data.ByteString.Internal (ByteString(PS))
import Data.Primitive (SmallArray,PrimArray,ByteArray(..))
import Data.Word (Word16)
import Control.Monad.ST (runST)
import Control.Monad.ST.Run (runPrimArrayST)
import GHC.ForeignPtr (ForeignPtrContents(PlainPtr))
import GHC.ForeignPtr (ForeignPtr(ForeignPtr))
import qualified GHC.Exts as Exts
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Parser as P
import qualified Data.Bytes.Parser.Latin as Latin
import qualified Data.Primitive as PM
import qualified Data.Attoparsec.ByteString.Char8 as Atto
import qualified Data.Aeson.Parser as Aeson
import qualified "scientific" Data.Scientific as SlowSci
import qualified "scientific-notation" Data.Number.Scientific as SCI
main :: IO ()
main = defaultMain
[ bgroup "scientific-notation"
[ bgroup "parser"
[ bench "ten-small"
(whnf (\b -> P.parseByteArray decodeTen b) tenSmall)
, bench "ten-large"
(whnf (\b -> P.parseByteArray decodeTen b) tenLarge)
]
, bgroup "conversion"
[ bench "twenty-word16"
(whnf (\b -> convertArray16 b) twentyFastSci)
]
]
, bgroup "scientific"
[ bgroup "parser"
[ bench "ten-small" $ whnf
(\b -> Atto.parseOnly
(aesonDecodeN 10 []) (fromPinned b)
) tenSmall
, bench "ten-large" $ whnf
(\b -> Atto.parseOnly
(aesonDecodeN 10 []) (fromPinned b)
) tenLarge
]
, bgroup "conversion"
[ bench "twenty-word16"
(whnf (\b -> convertSlowArray16 b) twentySlowSci)
]
]
]
-- TODO: In the test suite, we should confirm that parsing this
-- actually succeeds. We intentionally avoid leading plus signs
-- here so that we can compare against aeson.
tenSmall :: ByteArray
tenSmall = pin $ Bytes.toByteArray $ Bytes.fromAsciiString $ concat
[ ",4256"
, ",-125e14"
, ",5.000006"
, ",1e100"
, ",-13.25E-100"
, ",-653467618"
, ",-17e+6"
, ",9999.001"
, ",0000.002"
, ",0000.002E1"
]
-- TODO: In the test suite, we should confirm that parsing this
-- actually succeeds. We intentionally avoid leading plus signs
-- here so that we can compare against aeson.
tenLarge :: ByteArray
tenLarge = pin $ Bytes.toByteArray $ Bytes.fromAsciiString $ concat
[ ",4221465241250205246754620201240240201451991999956"
, ",242422432499393113113131313131533753.02031243210e13432"
, ",-0.999999999999999999999999999999999999"
, ",4.46246246526345643246256423645246224e100"
, ",42463523462.46246243246256423645246224E24625"
, ",-82463523462.56246243246256423645246224e-24625"
, ",82463523462.56246243246256423645246224e+24625"
, ",-201.562462432462564236452462240240420"
, ",-0.777777777777777777777777777777777e-777"
, ",0.987777777777777777777777777777777e-42"
]
-- All of these can fit inside a Word16.
twentyPairs :: SmallArray (Int,Int)
twentyPairs = Exts.fromList
[ (2336,0)
, (43265,0)
, (17,0)
, (24,3)
, (1,4)
, (25,0)
, (0,0)
, (1900,0)
, (65,0)
, (1100,0)
, (5,3)
, (0,0)
, (1600,0)
, (1500,0)
, (2000,0)
, (62,2)
, (500,0)
, (670,0)
, (1100,0)
, (65500,0)
]
twentyFastSci :: SmallArray SCI.Scientific
twentyFastSci = fmap (uncurry SCI.small) twentyPairs
twentySlowSci :: SmallArray SlowSci.Scientific
twentySlowSci = fmap
(\(x,y) -> SlowSci.scientific (fromIntegral x) y)
twentyPairs
aesonDecodeN :: Int -> [SlowSci.Scientific] -> Atto.Parser [SlowSci.Scientific]
aesonDecodeN !ix !acc = if ix > 0
then do
_ <- Atto.char ','
!num <- Aeson.scientific
aesonDecodeN (ix - 1) (num : acc)
else pure acc
decodeTen :: P.Parser () s (SmallArray SCI.Scientific)
decodeTen = do
arr <- P.effect (PM.newSmallArray 10 errorThunk)
let go !ix = if ix >= 0
then do
Latin.char () ','
!num <- SCI.parserSignedUtf8Bytes ()
P.effect (PM.writeSmallArray arr ix num)
go (ix - 1)
else P.effect (PM.unsafeFreezeSmallArray arr)
go 9
convertArray16 ::
SmallArray SCI.Scientific
-> PrimArray Word16
convertArray16 xs = runPrimArrayST $ do
let len = PM.sizeofSmallArray xs
ws <- PM.newPrimArray len
let go !ix = if ix >= 0
then case SCI.toWord16 (PM.indexSmallArray xs ix) of
Nothing -> error "convertArray16: bad number"
Just (r :: Word16) -> do
PM.writePrimArray ws ix r
go (ix - 1)
else PM.unsafeFreezePrimArray ws
go (len - 1)
convertSlowArray16 ::
SmallArray SlowSci.Scientific
-> PrimArray Word16
convertSlowArray16 xs = runPrimArrayST $ do
let len = PM.sizeofSmallArray xs
ws <- PM.newPrimArray len
let go !ix = if ix >= 0
then case SlowSci.toBoundedInteger (PM.indexSmallArray xs ix) of
Nothing -> error "convertArray16: bad number"
Just (r :: Word16) -> do
PM.writePrimArray ws ix r
go (ix - 1)
else PM.unsafeFreezePrimArray ws
go (len - 1)
errorThunk :: a
{-# noinline errorThunk #-}
errorThunk = error "scientific:benchmark error"
-- Convert a pinned immutable byte array to a bytestring.
fromPinned :: ByteArray -> ByteString
{-# inline fromPinned #-}
fromPinned (ByteArray arr# ) = PS
(ForeignPtr (Exts.byteArrayContents# arr# ) (PlainPtr (Exts.unsafeCoerce# arr#)))
0 (Exts.I# (Exts.sizeofByteArray# arr# ))
pin :: ByteArray -> ByteArray
pin src = runST $ do
let len = PM.sizeofByteArray src
dst <- PM.newByteArray len
PM.copyByteArray dst 0 src 0 len
PM.unsafeFreezeByteArray dst