Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ This runs with 2 concurrent queries, 10 times over:

This benchmark is unfair towards both hpgsql and postgresql-simple because the row decoder is Generically derived for them while it is hand-written for hasql.
```csv
postgresql-simple Record List (100000 rows),12.07,142.59M,99.3
hasql Record List (100000 rows),6.369,142.48M,79.0
hpgsql Record List (100000 rows),3.908,72.07M,119.9
postgresql-simple Record List (100000 rows),12.23,142.52M,91.3
hasql Record List (100000 rows),6.119,142.48M,78.5
hpgsql Record List (100000 rows),3.719,72.07M,120.2
```

### Materializing 100_000 rows with 13 columns each into a List of Tuples
Expand All @@ -55,9 +55,9 @@ However, Hpgsql's implementation streams directly from the socket while the othe
it might not be a fair comparison in terms of implementation (e.g. you can advance multiple
cursors simultaneously, but not hpgsql's Streamed-from-socket streams).
```csv
streaming-postgresql-simple Record Stream (100000 rows),13.59,73.34M,0.0
postgresql-simple Record fold (100000 rows),13.37,77.84M,0.0
hpgsql Record Stream (100000 rows),1.307,72.07M,0.0
streaming-postgresql-simple Record Stream (100000 rows),13.05,73.30M,0.0
postgresql-simple Record fold (100000 rows),12.23,76.22M,0.0
hpgsql Record Stream (100000 rows),1.117,72.07M,0.0
```

### Streaming 100_000 rows with 13 columns as Tuples
Expand Down
5 changes: 5 additions & 0 deletions Runfile
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,8 @@ tests-compat:
cabal build hpgsql-simple-compat-tests
nix-shell -A "shellPg${pg}" ./default.nix --run "./scripts/run-hpgsql-simple-compat-tests-db-internal.sh $TARGS"
done

ghc-core:
set -eo pipefail
cabal build hpgsql-tests 1>&2
cat dist-newstyle/build/x86_64-linux/ghc-9.10.3/hpgsql-tests-0.1.0.0/x/hpgsql-tests/build/hpgsql-tests/hpgsql-tests-tmp/RowDecoderGhcCore.thr.dump-simpl
2 changes: 2 additions & 0 deletions hpgsql-benchmarks/src/Main.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# OPTIONS_GHC -ddump-simpl -ddump-to-file #-}

module Main where

import Control.Concurrent.Async (mapConcurrently)
Expand Down
53 changes: 53 additions & 0 deletions hpgsql-tests/RowDecoderGhcCore.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{-# OPTIONS_GHC -ddump-simpl -ddump-to-file #-}

-- |
-- This is not a real test module. It's just a type deriving `FromPgRow`
-- so we can look at GHC Core output.
module RowDecoderGhcCore where

import Data.Int (Int64)
import Data.Text (Text)
import Data.Time (Day, UTCTime)
import GHC.Generics (Generic)
import Hpgsql.Encoding (FromPgRow (..), fieldDecoder, genericFromPgRow, singleField)

data BenchRow = BenchRow
{ brId :: !Int,
brDate1 :: !Day,
brDate2 :: !Day,
brTimestamp1 :: !UTCTime,
brTimestamp2 :: !UTCTime,
brText1 :: !Text,
brText2 :: !Text,
brDouble1 :: !Double,
brDouble2 :: !Double,
brMaybeInt :: !(Maybe Int),
brMaybeText :: !(Maybe Text),
brMaybeDouble :: !(Maybe Double),
brMaybeDay :: !(Maybe Day)
}

-- Generically deriving section.

deriving instance Generic BenchRow

instance FromPgRow BenchRow where
rowDecoder = genericFromPgRow

-- Hand-written applicative style deriving section.
-- instance FromPgRow BenchRow where
-- rowDecoder =
-- SomeRecord
-- <$> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
-- <*> singleField fieldDecoder
1 change: 1 addition & 0 deletions hpgsql-tests/hpgsql-tests.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ executable hpgsql-tests
ParsingSpec
PipelineSpec
PreparedStatementsSpec
RowDecoderGhcCore
SqlQuasiquoterSpec
TestUtils
ThreadSafetySpec
Expand Down
9 changes: 8 additions & 1 deletion hpgsql/src/Hpgsql/Encoding.hs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy as LBS
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import Data.Coerce (coerce)
import Data.Fixed (divMod')
import Data.Functor.Contravariant (Contravariant (..))
import Data.Int (Int16, Int32, Int64)
Expand Down Expand Up @@ -1192,13 +1193,19 @@ class ProductTypeDecoder f where
genRowDecoder :: RowDecoder (f a)

instance (ProductTypeDecoder a, ProductTypeDecoder b) => ProductTypeDecoder (a :*: b) where
{-# INLINE genRowDecoder #-}
genRowDecoder = (:*:) <$> genRowDecoder <*> genRowDecoder

instance (ProductTypeDecoder f) => ProductTypeDecoder (M1 a c f) where
{-# INLINE genRowDecoder #-}
genRowDecoder = M1 <$> genRowDecoder

instance (FromPgField a) => ProductTypeDecoder (K1 r a) where
genRowDecoder = fmap K1 $ singleField $ fieldDecoder @a
{-# INLINE genRowDecoder #-}
-- coercing instead of fmap reduces memory usage, apparently
-- by reducing (unnecessary) closures in the final row decoder,
-- as per looking at GHC Core
genRowDecoder = coerce $ singleField $ fieldDecoder @a

genericToPgRow :: forall a. (Generic a, ProductTypeEncoder (Rep a)) => RowEncoder a
genericToPgRow = contramap from genRowEncoder
Expand Down