diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 40c3616..8a741e7 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -31,18 +31,18 @@ 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.10,142.65M,120.5 -hasql Record List (100000 rows),6.314,142.48M,77.8 -hpgsql Record List (100000 rows),4.092,72.07M,119.7 +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 ``` ### Materializing 100_000 rows with 13 columns each into a List of Tuples This runs with 2 concurrent queries, 10 times over: ```csv -postgresql-simple Tuple List (100000 rows),14.74,142.52M,144.9 -hasql Tuple List (100000 rows),8.263,142.48M,202.2 -hpgsql Tuple List (100000 rows),4.648,72.07M,150.4 +postgresql-simple Tuple List (100000 rows),14.37,142.54M,137.7 +hasql Tuple List (100000 rows),8.305,142.48M,201.3 +hpgsql Tuple List (100000 rows),4.541,72.07M,150.5 ``` ### Streaming 100_000 rows with 13 columns as Records @@ -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.32,73.41M,0.0 -postgresql-simple Record fold (100000 rows),13.47,77.81M,0.0 -hpgsql Record Stream (100000 rows),1.421,72.07M,0.0 +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 100_000 rows with 13 columns as Tuples @@ -67,9 +67,9 @@ Hpgsql's implementation streams directly from the socket while the others use cu 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 Tuple Stream (100000 rows),14.10,73.26M,0.0 -postgresql-simple Tuple fold (100000 rows),13.45,84.42M,0.0 -hpgsql Tuple Stream (100000 rows),1.025,72.07M,0.0 +streaming-postgresql-simple Tuple Stream (100000 rows),14.04,73.37M,0.0 +postgresql-simple Tuple fold (100000 rows),13.53,82.45M,0.0 +hpgsql Tuple Stream (100000 rows),880.0,72.07M,0.0 ``` ### COPY FROM STDIN @@ -77,6 +77,6 @@ hpgsql Tuple Stream (100000 rows),1.025,72.07M,0.0 This compares hpgsql's binary copy to a `forM` loop writing text rows. ```csv -postgresql-simple text COPY (100000 rows),1.348,72.10M,3.8 -hpgsql copyFromS binary COPY (100000 rows),672.1,72.07M,10.8 +postgresql-simple text COPY (100000 rows),1.373,72.10M,3.8 +hpgsql copyFromS binary COPY (100000 rows),652.0,72.07M,10.8 ``` diff --git a/README.md b/README.md index 03ecaa5..bee65fe 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ You should start by swapping all of "postgresql-simple", "postgresql-libpq", and ## Performance -Some benchmarks show materializing large query results with hpgsql takes 31-34% the time postgresql-simple takes, and 55-65% the time hasql takes (on my computer, Linux x64, GHC 9.10.3, compiled with -O1). +Some benchmarks show materializing large query results with hpgsql takes 28-32% the time postgresql-simple takes, and 55-62% the time hasql takes (on my computer, Linux x64, GHC 9.10.3, compiled with -O1). -When comparing hpgsql's Stream querying, hpgsql takes 9-11% the time of both [streaming-postgresql-simple](https://hackage.haskell.org/package/streaming-postgresql-simple) and postgresql-simple's cursor folding functions, although this might not be a fair comparison for some use cases. +When comparing hpgsql's Stream querying, hpgsql takes 7-11% the time of both [streaming-postgresql-simple](https://hackage.haskell.org/package/streaming-postgresql-simple) and postgresql-simple's cursor folding functions, although this might not be a fair comparison for some use cases. hpgsql's binary COPY runs in ~50% the time of postgresql-simple's textual COPY. diff --git a/hpgsql/src/Hpgsql/Encoding.hs b/hpgsql/src/Hpgsql/Encoding.hs index b03a83c..f653109 100644 --- a/hpgsql/src/Hpgsql/Encoding.hs +++ b/hpgsql/src/Hpgsql/Encoding.hs @@ -150,6 +150,7 @@ data RowDecoder a = RowDecoder instance Applicative RowDecoder where pure v = RowDecoder (const $ pure v) (map (,True)) 0 + {-# INLINE (<*>) #-} -- This is crucial for performance. It makes our CPS Parser truly compile to CPS row decoders. RowDecoder p1 tc1 nc1 <*> RowDecoder p2 tc2 nc2 = RowDecoder (\colTypes -> let (cols1, cols2) = List.splitAt nc1 colTypes in p1 cols1 <*> p2 cols2) (\colTypes -> let (cols1, cols2) = List.splitAt nc1 colTypes in tc1 cols1 ++ tc2 cols2) (nc1 + nc2) instance (TypeError (TypeLits.Text "RowDecoder does not have a Monad instance in Hpgsql because Hpgsql type-checks the result types of queries before having access to even the first data row. Use the Applicative class to write your instances or use the Monadic decoding variants.")) => Monad RowDecoder where diff --git a/hpgsql/src/Hpgsql/SimpleParser.hs b/hpgsql/src/Hpgsql/SimpleParser.hs index b8f1c0a..64a13bd 100644 --- a/hpgsql/src/Hpgsql/SimpleParser.hs +++ b/hpgsql/src/Hpgsql/SimpleParser.hs @@ -3,8 +3,12 @@ -- and perform better than attoparsec, at least the way we use it in -- hpgsql. -- --- In benchmarks, this can improve performance by 12-15% materializing --- query results. +-- In benchmarks, this improved performance by 12-15% materializing +-- query results when it was introduced. +-- With the INLINE pragma in RowDecoder's Applicative's (<*>), GHC's +-- inliner was finally able to make full use of continuation passing, +-- and performance was improved by another ~14.3%, with total memory +-- allocations reduced by ~6%. module Hpgsql.SimpleParser ( Parser (..), ParseResult (..),