Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codec): Introduce Decoder/Encoder traits #208

Merged
merged 14 commits into from
Jan 13, 2020

Conversation

alce
Copy link
Collaborator

@alce alce commented Dec 23, 2019

In preparation for experimenting with a different decoding strategy, this PR swaps tokio's Decoder and Encoder traits with our own.

@LucioFranco
Copy link
Member

@alce friendly ping, anything I can help here?

@alce
Copy link
Collaborator Author

alce commented Jan 4, 2020 via email

@alce alce changed the title define Decoder and Encoder traits [WIP] use BufList for decoding Jan 11, 2020
tonic/src/codec/mod.rs Outdated Show resolved Hide resolved
@LucioFranco LucioFranco added this to the 0.1 milestone Jan 11, 2020
@LucioFranco LucioFranco self-assigned this Jan 11, 2020
@LucioFranco
Copy link
Member

So I brought this branch inline with latest master and ran benchmarks:

 name              old ns/iter        new ns/iter        diff ns/iter  diff %  speedup
 chunk_size_100    967 (1039 MB/s)    1,169 (859 MB/s)            202  20.89%   x 0.83
 chunk_size_1005   368 (2730 MB/s)    394 (2550 MB/s)              26   7.07%   x 0.93
 chunk_size_500    481 (2089 MB/s)    532 (1889 MB/s)              51  10.60%   x 0.90
 message_count_1   364 (1387 MB/s)    393 (1284 MB/s)              29   7.97%   x 0.93
 message_count_10  1,551 (3255 MB/s)  1,860 (2715 MB/s)           309  19.92%   x 0.83
 message_count_20  2,882 (3504 MB/s)  3,436 (2939 MB/s)           554  19.22%   x 0.84
 message_size_10k  2,140 (9350 MB/s)  2,129 (9398 MB/s)           -11  -0.51%   x 1.01
 message_size_1k   512 (3925 MB/s)    543 (3701 MB/s)              31   6.05%   x 0.94
 message_size_5k   1,140 (8780 MB/s)  1,206 (8300 MB/s)            66   5.79%   x 0.95

Old is #6673f9 and new is this branch with a merge of that commit.

It looks like it may not be worth it or we should spend some more time on this.

@alce alce changed the title [WIP] use BufList for decoding use specialized buffers for encoding/decoding Jan 12, 2020
Copy link
Member

@LucioFranco LucioFranco left a comment

Choose a reason for hiding this comment

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

Perfect! Thanks!

@LucioFranco LucioFranco changed the title use specialized buffers for encoding/decoding feat(codec): Introduce Decoder/Encoder traits Jan 13, 2020
@LucioFranco LucioFranco merged commit 0fa2bf1 into hyperium:master Jan 13, 2020
@alce alce deleted the decode-experiment branch January 14, 2020 00:08
@magnet
Copy link
Contributor

magnet commented Jan 16, 2020

I haven't dug into this much, but while updating to tonic 0.1 this has bitten ne as it seems this is preventing me from doing zero-copy handling of Bytes like I used to do by hiding the Bytes/BytesMut type. I previously used tokio's BytesCodec and passed buffers (containing a flatbuffers payload) to tonic in a zero-copy manner. Has this ability been lost?

@magnet
Copy link
Contributor

magnet commented Jan 16, 2020

Here's the raw byte codec I was using on tonic beta. I was mostly delegating to BytesCodec. The encoder still allocated with reserve() and then copied the buffer I want to send. That's something I can do with the new design.

However I had the decoder do zero-copy splitting of BytesMut -- e.g the byte buffer returned by tonic was reused in my application code, and that's something that is not possible with the new DecodeBuf trait because the internal &mut BytesMut reference is hidden, and the split_to method hidden (and not available on bytes::Buf).

Previously:

#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct FlatbuffersCodec(());

impl FlatbuffersCodec {
    pub(crate) const fn new() -> Self {
        FlatbuffersCodec(())
    }
}

impl Codec for FlatbuffersCodec {
    type Encode = Bytes;
    type Decode = BytesMut;

    type Encoder = Self;
    type Decoder = Self;

    fn encoder(&mut self) -> Self::Encoder {
        FlatbuffersCodec::new()
    }

    fn decoder(&mut self) -> Self::Decoder {
        FlatbuffersCodec::new()
    }
}

impl Encoder for FlatbuffersCodec {
    type Item = Bytes;
    type Error = Status;

    fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<(), Self::Error> {
        BytesCodec::new().encode(item, buf).map_err(from_io_error)
    }
}

impl Decoder for FlatbuffersCodec {
    type Item = BytesMut;
    type Error = Status;

    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        BytesCodec::new().decode(buf).map_err(from_io_error)
    }
}

fn from_io_error(error: std::io::Error) -> Status {
    // Map Protobuf parse errors to an INTERNAL status code, as per
    // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
    Status::new(Code::Internal, error.to_string())
}

With the new trait design:

#[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct FlatbuffersCodec(());

impl FlatbuffersCodec {
    pub(crate) const fn new() -> Self {
        FlatbuffersCodec(())
    }
}

impl Codec for FlatbuffersCodec {
    type Encode = Bytes;
    type Decode = BytesMut;

    type Encoder = Self;
    type Decoder = Self;

    fn encoder(&mut self) -> Self::Encoder {
        FlatbuffersCodec::new()
    }

    fn decoder(&mut self) -> Self::Decoder {
        FlatbuffersCodec::new()
    }
}

impl Encoder for FlatbuffersCodec {
    type Item = Bytes;
    type Error = Status;

    fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
        // this is the same as before
        buf.reserve(item.len());
        buf.put(item);
        Ok(())
    }
}

impl Decoder for FlatbuffersCodec {
    type Item = BytesMut;
    type Error = Status;

    fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
        if buf.remaining() > 0 {
            let mut res = BytesMut::with_capacity(buf.remaining()); // This is now an extra alloc
            res.put_slice(buf.bytes());
            Ok(Some(res))
        } else {
            Ok(None)
        }
    }
}

@davedbase
Copy link

@magnet did you find a solution to this?

@magnet
Copy link
Contributor

magnet commented Dec 8, 2020

I haven't, but I have been busy with other stuff since.

@davedbase
Copy link

Any chance you are going to continue with it @magnet? This is a pretty awesome addition. I'm assuming that the new trait design totally prohibits you though. Would Tonic need changes to make this possible?

@magnet
Copy link
Contributor

magnet commented Dec 8, 2020

Not in the short term, no, maybe I'll pick this up sometimes next year.

themaxdavitt added a commit to themaxdavitt/tonic that referenced this pull request Sep 23, 2021
Pull request hyperium#208 introduced `tonic::codec::{DecodeBuf, Decoder, EncodeBuf, Encoder}`
in preparation for experimenting with a different decoding strategy, but as it
currently stands the in-tree functionality is equivalent to the original
implementation using `tokio_util::codec::{Decoder, Encoder}` and
`bytes::BytesMut`. Given that a significant amount of time has passed with no
experimentation having happened (as far as I can tell), this commit reverts
those changes. Additionally, it also restores efficient use of the underlying
buffers, an issue that was brought up later in its respective comment thread.
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.

4 participants