Skip to content

Specialized row decoders per type - #48

Draft
mzabani wants to merge 10 commits into
cps-parser-fewer-bytestringsfrom
specialized-row-decoders-per-type
Draft

Specialized row decoders per type#48
mzabani wants to merge 10 commits into
cps-parser-fewer-bytestringsfrom
specialized-row-decoders-per-type

Conversation

@mzabani

@mzabani mzabani commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Part 3 of perf optimizations. This has API changes (most likely not breaking) and still needs a lot of thinking before being merged.

The main idea here is to spread usage of our CPS Parser all the way down to decoders by adding RowDecoder-returning methods to the FromPgFieldtypeclass.
Together with the previous PR (ByteStringIdx passing), we can avoid creating instances of ByteString we currently need due to fieldDecoder requiring it (fieldValueDecoder :: FieldInfo -> Maybe ByteString -> Either String a).

If we're willing to break backwards compatibility a bit, we could try to change that to fieldValueDecoder :: FieldInfo -> PgValue -> Either String a and make PgValue something like SqlNull | NotNull !ByteString !ByteStringIdx !ByteStringIdx: a slice of the original buffer/bytestring, which guarantees no copies even when using this method. Having this custom type would be breaking now, but if we hide its internals it will give us room to experiment changing the representation in the future (e.g. I'm seriously considering switching from ByteString to ShortByteString).

However, this still wouldn't allow for some niche optimizations like our small-value decoding decodePgFieldWithAtMost4Bytes, because that reads 8 bytes and needs to have the parser position/ByteStringIdx pointing to the field length, not the field's value itself.

There is also a matter of optimizing Maybe a instances, which is tricky. Currently OVERLAPPING instances have worked for base types, but we would want something that extends naturally to user defined types without asking them to create OVERLAPPING instances themselves (not to mention they'd need "awkward" decoding functions or access to our CPS Parser, which is still internal).

It's best to solve the Maybe a problem first, because that's a problem regardless of the rest.

Before merging:

  • Verify the situation with FromPgField (Maybe a) specialized row decoders
  • Double-check Generically derived FromPgRow instances don't blow up in size
  • Does it make sense to even have a FieldDecoder type? It's much nicer for implementers than implementing RowDecoder, but maybe we can provide functions to facilitate the latter.
  • It makes sense not to merge this until we finish the ShortByteString experiment. Because if we're going to break some form of API compatibility, it's best we do it only once. And then, we can try the strict SqlNull | NotNull !ShortByteString experiment again after all these improvements, where it might finally make things better, not worse.
  • Consider this change: FromPgField a has a method RowDecoder (Maybe a) instead of RowDecoder a.
  • If we're going to have specialized row decoders per type, why not move the RowDecoder-returning method to the FieldDecoder type? That makes field composition benefit from it.
  • Look at the instance FromPgField MyEnum's definition, and see that users have no way of using fail with rawBytesFieldDecoder. Maybe something to keep in mind in any form of API changes.
  • Check the diff in the tests project, benchmarks and hpgsql-simple-compat, to understand the extent of changes users of hpgsql will have to make (particularly if we break API compatibility)

mzabani added 10 commits August 12, 2026 14:49
We're beginning to converge to row decoders not being inlined by
default, but their implementations/bodies being as inlined as possible,
which feels reasonable.

It remains to be seen if we can add a super-inlined version of row
decoders for users to choose from if they wish, and what the effects
are.
The boundary here is very nice: users that want to avoid too much code
bloat can use the non-inlined decoders, and those that want to max out
performance can use the inlined decoders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant