Skip to content

Conversation

@phadej
Copy link
Collaborator

@phadej phadej commented Aug 13, 2025

No description provided.

@phadej phadej requested a review from dcoutts August 13, 2025 18:40
@phadej
Copy link
Collaborator Author

phadej commented Aug 13, 2025

I couldn't run macro benchmarks (#362), there are no differences in micro benchmarks

@phadej phadej force-pushed the issue-355-grab-contents branch from c5e3fc5 to 60e1a8c Compare August 14, 2025 13:42
Copy link
Member

@dcoutts dcoutts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts so far.

Generally this looks very promising: providing the feature at low cost to non-users.


SlowConsumeTokenString bs' k len -> do
(bstr, bs'') <- getTokenVarLen len bs' offset'
(bstr, bs'', marks') <- getTokenVarLen len bs' (offset' + intToInt64 (BS.length bs')) marks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand yet why we need to change the offset calculation here.

If I've followed correctly then:

offset' = offset + intToInt64 (BS.length bs - BS.length bs')
offset'' = offset' + intToInt64 (BS.length bs')
         = offset + intToInt64 (BS.length bs)

(which might therefore be a simpler definition)

But I don't see why it's the right offset.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • offset' is a the beginning of bs''.
  • getTokenVarLen expect offset to be after, thus + BS.length bs'.

The getTokenVarLen precondition is in its comments.

I can change offset'' = offset + intToInt64 (BS.length bs), say so.


Why we need to change offset in the first place? Because getTokenVarLen didn't use it for anything else than error reporting, so it was "wrong".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks. And thanks for pushing the slight simplification.

case mbs of
Nothing -> decodeFail bs' offset' "end of input"
Just bs'' -> go_slow da' bs'' offset'
Just bs'' -> go_slow da' bs'' offset' (slowMarkChunk bs'' offset' marks)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so here we accumulate the input chunk, but only if we have any marks. So here is where everyone pays a cost even if they're not using the feature, but it's very minimal and only on chunk boundaries.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I doubt there's any noticeable cost cause

slowMarkChunk :: ByteString -> ByteOffset -> Marks -> Marks
slowMarkChunk _  _    NoMarks            = NoMarks

My gut feeling is there's more accumulated cost of threading an additional NoMarks argument through the interpreter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. And as you noted, the micro-benchmarks show nothing measureable. And I fixed the macro-benchmarks and they also show nothing measurable.

getTokenVarLenSlow :: [ByteString] -> Int -> ByteOffset
-> IncrementalDecoder s (ByteString, ByteString)
getTokenVarLenSlow bss n offset = do
(offset + intToInt64 (BS.length bs'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not followed why we're changing the offset calculations here, or is it just shuffling around where we do it, from caller to callee?

Copy link
Collaborator Author

@phadej phadej Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not followed why we're changing the offset calculations here

Because getTokenVarLen/Slow didn't use offset for anything "functional", only to report an error at. Previously (as you can see from the diff) it wasn't updated at all even after new chunks were read.

A side-effect of this change is that "end-of-input" error will now report the actualy end-of-input offset, previously it reported an offset at which getTokenVarLen was called.

res = either throw snd (deserialiseFromBytes emptyDecoder (LBS.pack [0]))

empty_deserialise_fail :: Bool
empty_deserialise_fail = isLeft (deserialiseFromBytes emptyDecoder LBS.empty)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these tests for both peekByteOffset and getInputSpan.

I find it a bit surprising as peekByteOffset as well as unmarkInput and getInputSpan do succeed at the end of input, but they seem to fail at the very beginning of an empty input.

This is quite obscure corner case, but nevertheless I think it's good to be aware off (i.e. have test for it).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these tests. Yes it is obscure and I doubt it's intentional. Perhaps we should note here more clearly that although this is the current behaviour, it's not necessarily ideal and could be reviewed and changed to something more regular.

@phadej phadej force-pushed the issue-355-grab-contents branch from 01cf105 to 00f95af Compare August 15, 2025 12:12
Copy link
Member

@dcoutts dcoutts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. It adds the feature nicely, and doesn't add any cost for users not using it. And indeed using the feature itself is pretty cheap (as measured by a macro-benchmark).

I've taken the liberty of doing some renaming. And I'll add more API docs. I'll merge once I've finished adding docs.

res = either throw snd (deserialiseFromBytes emptyDecoder (LBS.pack [0]))

empty_deserialise_fail :: Bool
empty_deserialise_fail = isLeft (deserialiseFromBytes emptyDecoder LBS.empty)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these tests. Yes it is obscure and I doubt it's intentional. Perhaps we should note here more clearly that although this is the current behaviour, it's not necessarily ideal and could be reviewed and changed to something more regular.


SlowConsumeTokenString bs' k len -> do
(bstr, bs'') <- getTokenVarLen len bs' offset'
(bstr, bs'', marks') <- getTokenVarLen len bs' (offset' + intToInt64 (BS.length bs')) marks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks. And thanks for pushing the slight simplification.

case mbs of
Nothing -> decodeFail bs' offset' "end of input"
Just bs'' -> go_slow da' bs'' offset'
Just bs'' -> go_slow da' bs'' offset' (slowMarkChunk bs'' offset' marks)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. And as you noted, the micro-benchmarks show nothing measureable. And I fixed the macro-benchmarks and they also show nothing measurable.

@dcoutts dcoutts force-pushed the issue-355-grab-contents branch from cc0094d to c0d86d7 Compare October 2, 2025 14:45
-- > openByteSpan
-- > x <- decode
-- > !after <- peekByteSpan
-- > bytes <- peekByteSpan
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you removed bang here, but not in the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I think the bang there is unnecessary and it's better to force the call of peekMarkedByteSpan as it is passed to the continuation. I'll change that.

Change:
markInput    --> openByteSpan
unmarkInput  --> closeByteSpan
getInputSpan --> peekByteSpan
@dcoutts dcoutts force-pushed the issue-355-grab-contents branch from c0d86d7 to 4d47bd5 Compare October 2, 2025 21:38
@dcoutts dcoutts merged commit 72a0e73 into master Oct 2, 2025
15 checks passed
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.

3 participants