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

Consider making SizeLimit a trait rather than an enum #121

Closed
dtolnay opened this issue Feb 23, 2017 · 7 comments
Closed

Consider making SizeLimit a trait rather than an enum #121

dtolnay opened this issue Feb 23, 2017 · 7 comments

Comments

@dtolnay
Copy link
Collaborator

@dtolnay dtolnay commented Feb 23, 2017

-pub struct Deserializer<R> {
+pub struct Deserializer<R, L> {
     reader: R,
-    size_limit: SizeLimit,
+    size_limit: L,
     read: u64
 }

This way the Infinite case would not be slowed down by all the branching in Deserializer::read_bytes:

match self.size_limit {
    SizeLimit::Infinite => Ok(()),
    SizeLimit::Bounded(x) if self.read <= x => Ok(()),
    SizeLimit::Bounded(_) => Err(ErrorKind::SizeLimit.into())
}

Something like:

trait SizeLimit {
    fn check(&self, read: u64) -> Result<()>;
}

struct Infinite;
impl SizeLimit for Infinite {
    #[inline]
    fn check(&self, _read: u64) -> Result<()> {
        Ok(())
    }
}

struct Bounded(u64);
impl SizeLimit for Bounded {
    fn check(&self, read: u64) -> Result<()> {
        if read <= self.0 {
            Ok(())
        } else {
            Err(ErrorKind::SizeLimit.into())
        }
    }
}

impl<R: Read, L: SizeLimit> Deserializer<R, L> {
    fn read_bytes(&mut self, count: u64) -> Result<()> {
        self.read += count;
        self.size_limit.check(self.read)
    }
}

The compiler may be able to generate faster code in the Infinite case knowing that no branching is happening.

@dtolnay
Copy link
Collaborator Author

@dtolnay dtolnay commented Feb 23, 2017

A possible further advantage would be in the Infinite case, the size_of Deserializer drops from 40 bytes to 24 bytes (assuming reading from &[u8]) and in the Bounded case it drops to 32 bytes.

&[u8] // 16 bytes
bincode::SizeLimit // 16 bytes :(
u64 // 8 bytes
@TyOverby
Copy link
Collaborator

@TyOverby TyOverby commented Feb 23, 2017

I was actually just thinking about this!

You could even go further and have the trait be something like:

trait SizeLimit {
    type Storage;
    fn create_storage() -> Self::Storage;
    fn add(&self, size: u64, storage: &mut Storage) -> Result<()>;
}

Then the Deserializer would look like

struct Deserializer<R: Read, L: SizeLimit> {
   reader: R,
   limit: L,
   storage: L::Storage,

This way, the implementation of Infinite would be

impl SizeLimit for Infinite {
    type Storage = ();
    fn add(&self, size: &u64, storage: &mut ()) -> Result<()> {}
@TyOverby
Copy link
Collaborator

@TyOverby TyOverby commented Feb 23, 2017

^ Then sizeof(Deserializer<R, Infinite>) == sizeof(R)

@dtolnay
Copy link
Collaborator Author

@dtolnay dtolnay commented Feb 23, 2017

A simpler / possibly faster alternative:

trait SizeLimit {
    fn add(&mut self, n: u64) -> Result<()>;
}

That way it doesn't need a pointer dereference for both self and storage.

@TyOverby
Copy link
Collaborator

@TyOverby TyOverby commented Feb 23, 2017

but then implementors of SizeLimit would need to carry their own storage. This would make struct Bounded(u64) not work.

In addition, I don't think any implementors of SizeLimit would ever dereference self.

@dtolnay
Copy link
Collaborator Author

@dtolnay dtolnay commented Feb 23, 2017

  • struct Bounded(u64) would work:

    impl SizeLimit for Bounded {
        fn add(&mut self, n: u64) -> Result<()> {
            if self.0 >= n {
                self.0 -= n;
                Ok(())
            } else {
                Err(...)
            }
        }
    }
  • Bounded would need to dereference self in order to find out what the bound is, right...? You were envisioning storing the bound in (*self).0 and storing the current pos in *storage.

@TyOverby
Copy link
Collaborator

@TyOverby TyOverby commented Feb 23, 2017

Oh yeah, I suppose that does work. I was thinking of how I could expand this to also do structure size estimation, but I think I know how to deal with that now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.