Skip to content

Commit

Permalink
perf: remove Bytes8 abstraction with 50% improvement (#120)
Browse files Browse the repository at this point in the history
```
## master
test req/req ... bench:                     241 ns/iter (+/- 2)
test req_short/req_short ... bench:         32 ns/iter (+/- 1)
test resp/resp ... bench:                   228 ns/iter (+/- 3)
test resp_short/resp_short ... bench:       40 ns/iter (+/- 0)

## PR #120 + other tweaks
test req/req ... bench:                     181 ns/iter (+/- 2)
test req_short/req_short ... bench:         25 ns/iter (+/- 4)
test resp/resp ... bench:                   183 ns/iter (+/- 6)
test resp_short/resp_short ... bench:       32 ns/iter (+/- 0)
```
  • Loading branch information
AaronO committed Aug 29, 2022
1 parent 5f8a7b4 commit 0e1e0b0
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 164 deletions.
140 changes: 4 additions & 136 deletions src/iter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::slice;
use core::convert::TryInto;
use core::convert::TryFrom;

pub struct Bytes<'a> {
slice: &'a [u8],
Expand Down Expand Up @@ -30,12 +32,8 @@ impl<'a> Bytes<'a> {
}

#[inline]
pub fn peek_4(&self) -> Option<&[u8]> {
if self.slice.len() >= self.pos + 4 {
Some(&self.slice[self.pos..=self.pos + 3])
} else {
None
}
pub fn peek_n<U: TryFrom<&'a[u8]>>(&self, n: usize) -> Option<U> {
self.slice.get(self.pos..self.pos + n)?.try_into().ok()
}

#[inline]
Expand Down Expand Up @@ -85,15 +83,6 @@ impl<'a> Bytes<'a> {
self.pos = 0;
self.slice = tail;
}

#[inline]
pub fn next_8<'b>(&'b mut self) -> Option<Bytes8<'b, 'a>> {
if self.slice.len() >= self.pos + 8 {
Some(Bytes8::new(self))
} else {
None
}
}
}

impl<'a> AsRef<[u8]> for Bytes<'a> {
Expand All @@ -117,124 +106,3 @@ impl<'a> Iterator for Bytes<'a> {
}
}
}

pub struct Bytes8<'a, 'b: 'a> {
bytes: &'a mut Bytes<'b>,
#[cfg(debug_assertions)]
pos: usize
}

macro_rules! bytes8_methods {
($f:ident, $pos:expr) => {
#[inline]
pub fn $f(&mut self) -> u8 {
self.assert_pos($pos);
let b = unsafe { *self.bytes.slice.get_unchecked(self.bytes.pos) };
self.bytes.pos += 1;
b
}
};
() => {
bytes8_methods!(_0, 0);
bytes8_methods!(_1, 1);
bytes8_methods!(_2, 2);
bytes8_methods!(_3, 3);
bytes8_methods!(_4, 4);
bytes8_methods!(_5, 5);
bytes8_methods!(_6, 6);
bytes8_methods!(_7, 7);
}
}

impl<'a, 'b: 'a> Bytes8<'a, 'b> {
bytes8_methods! {}

#[cfg(not(debug_assertions))]
#[inline]
fn new(bytes: &'a mut Bytes<'b>) -> Bytes8<'a, 'b> {
Bytes8 {
bytes: bytes,
}
}

#[cfg(debug_assertions)]
#[inline]
fn new(bytes: &'a mut Bytes<'b>) -> Bytes8<'a, 'b> {
Bytes8 {
bytes,
pos: 0,
}
}

#[cfg(not(debug_assertions))]
#[inline]
fn assert_pos(&mut self, _pos: usize) {
}

#[cfg(debug_assertions)]
#[inline]
fn assert_pos(&mut self, pos: usize) {
assert!(self.pos == pos);
self.pos += 1;
}
}

#[cfg(test)]
mod tests {
use super::Bytes;

#[test]
fn test_next_8_too_short() {
// Start with 10 bytes.
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
let mut bytes = Bytes::new(&slice);
// Skip 3 of them.
unsafe { bytes.advance(3); }
// There should be 7 left, not enough to call next_8.
assert!(bytes.next_8().is_none());
}

#[test]
fn test_next_8_just_right() {
// Start with 10 bytes.
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
let mut bytes = Bytes::new(&slice);
// Skip 2 of them.
unsafe { bytes.advance(2); }
// There should be 8 left, just enough to call next_8.
let ret = bytes.next_8();
assert!(ret.is_some());
let mut ret = ret.unwrap();
// They should be the bytes starting with 2.
assert_eq!(ret._0(), 2u8);
assert_eq!(ret._1(), 3u8);
assert_eq!(ret._2(), 4u8);
assert_eq!(ret._3(), 5u8);
assert_eq!(ret._4(), 6u8);
assert_eq!(ret._5(), 7u8);
assert_eq!(ret._6(), 8u8);
assert_eq!(ret._7(), 9u8);
}

#[test]
fn test_next_8_extra() {
// Start with 10 bytes.
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
let mut bytes = Bytes::new(&slice);
// Skip 1 of them.
unsafe { bytes.advance(1); }
// There should be 9 left, more than enough to call next_8.
let ret = bytes.next_8();
assert!(ret.is_some());
let mut ret = ret.unwrap();
// They should be the bytes starting with 1.
assert_eq!(ret._0(), 1u8);
assert_eq!(ret._1(), 2u8);
assert_eq!(ret._2(), 3u8);
assert_eq!(ret._3(), 4u8);
assert_eq!(ret._4(), 5u8);
assert_eq!(ret._5(), 6u8);
assert_eq!(ret._6(), 7u8);
assert_eq!(ret._7(), 8u8);
}
}
53 changes: 25 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,14 +478,16 @@ impl<'h, 'b> Request<'h, 'b> {
let orig_len = buf.len();
let mut bytes = Bytes::new(buf);
complete!(skip_empty_lines(&mut bytes));
let method = match bytes.peek_4() {
Some(b"GET ") => {
const GET: [u8; 4] = *b"GET ";
const POST: [u8; 4] = *b"POST";
let method = match bytes.peek_n::<[u8; 4]>(4) {
Some(GET) => {
unsafe {
bytes.advance_and_commit(4);
}
"GET"
}
Some(b"POST") if bytes.peek_ahead(4) == Some(b' ') => {
Some(POST) if bytes.peek_ahead(4) == Some(b' ') => {
unsafe {
bytes.advance_and_commit(5);
}
Expand Down Expand Up @@ -744,20 +746,13 @@ pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" };

#[inline]
fn parse_version(bytes: &mut Bytes) -> Result<u8> {
if let Some(mut eight) = bytes.next_8() {
expect!(eight._0() => b'H' |? Err(Error::Version));
expect!(eight._1() => b'T' |? Err(Error::Version));
expect!(eight._2() => b'T' |? Err(Error::Version));
expect!(eight._3() => b'P' |? Err(Error::Version));
expect!(eight._4() => b'/' |? Err(Error::Version));
expect!(eight._5() => b'1' |? Err(Error::Version));
expect!(eight._6() => b'.' |? Err(Error::Version));
let v = match eight._7() {
b'0' => 0,
b'1' => 1,
_ => return Err(Error::Version)
};
return Ok(Status::Complete(v))
if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) {
unsafe { bytes.advance(8); }
return match &eight {
b"HTTP/1.0" => Ok(Status::Complete(0)),
b"HTTP/1.1" => Ok(Status::Complete(1)),
_ => Err(Error::Version),
}
}

// else (but not in `else` because of borrow checker)
Expand Down Expand Up @@ -1117,24 +1112,26 @@ fn parse_headers_iter_uninit<'a, 'b>(
simd::match_header_value_vectored(bytes);

'value_line: loop {
if let Some(mut bytes8) = bytes.next_8() {
if let Some(bytes8) = bytes.peek_n::<[u8; 8]>(8) {
macro_rules! check {
($bytes:ident, $i:ident) => ({
b = $bytes.$i();
($bytes:ident, $i:literal) => ({
b = $bytes[$i];
if !is_header_value_token(b) {
unsafe { bytes.advance($i + 1); }
break 'value_line;
}
});
}

check!(bytes8, _0);
check!(bytes8, _1);
check!(bytes8, _2);
check!(bytes8, _3);
check!(bytes8, _4);
check!(bytes8, _5);
check!(bytes8, _6);
check!(bytes8, _7);
check!(bytes8, 0);
check!(bytes8, 1);
check!(bytes8, 2);
check!(bytes8, 3);
check!(bytes8, 4);
check!(bytes8, 5);
check!(bytes8, 6);
check!(bytes8, 7);
unsafe { bytes.advance(8); }

continue 'value_line;
}
Expand Down

0 comments on commit 0e1e0b0

Please sign in to comment.