Skip to content

Commit 0e1e0b0

Browse files
authored
perf: remove Bytes8 abstraction with 50% improvement (#120)
``` ## 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) ```
1 parent 5f8a7b4 commit 0e1e0b0

File tree

2 files changed

+29
-164
lines changed

2 files changed

+29
-164
lines changed

src/iter.rs

Lines changed: 4 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use core::slice;
2+
use core::convert::TryInto;
3+
use core::convert::TryFrom;
24

35
pub struct Bytes<'a> {
46
slice: &'a [u8],
@@ -30,12 +32,8 @@ impl<'a> Bytes<'a> {
3032
}
3133

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

4139
#[inline]
@@ -85,15 +83,6 @@ impl<'a> Bytes<'a> {
8583
self.pos = 0;
8684
self.slice = tail;
8785
}
88-
89-
#[inline]
90-
pub fn next_8<'b>(&'b mut self) -> Option<Bytes8<'b, 'a>> {
91-
if self.slice.len() >= self.pos + 8 {
92-
Some(Bytes8::new(self))
93-
} else {
94-
None
95-
}
96-
}
9786
}
9887

9988
impl<'a> AsRef<[u8]> for Bytes<'a> {
@@ -117,124 +106,3 @@ impl<'a> Iterator for Bytes<'a> {
117106
}
118107
}
119108
}
120-
121-
pub struct Bytes8<'a, 'b: 'a> {
122-
bytes: &'a mut Bytes<'b>,
123-
#[cfg(debug_assertions)]
124-
pos: usize
125-
}
126-
127-
macro_rules! bytes8_methods {
128-
($f:ident, $pos:expr) => {
129-
#[inline]
130-
pub fn $f(&mut self) -> u8 {
131-
self.assert_pos($pos);
132-
let b = unsafe { *self.bytes.slice.get_unchecked(self.bytes.pos) };
133-
self.bytes.pos += 1;
134-
b
135-
}
136-
};
137-
() => {
138-
bytes8_methods!(_0, 0);
139-
bytes8_methods!(_1, 1);
140-
bytes8_methods!(_2, 2);
141-
bytes8_methods!(_3, 3);
142-
bytes8_methods!(_4, 4);
143-
bytes8_methods!(_5, 5);
144-
bytes8_methods!(_6, 6);
145-
bytes8_methods!(_7, 7);
146-
}
147-
}
148-
149-
impl<'a, 'b: 'a> Bytes8<'a, 'b> {
150-
bytes8_methods! {}
151-
152-
#[cfg(not(debug_assertions))]
153-
#[inline]
154-
fn new(bytes: &'a mut Bytes<'b>) -> Bytes8<'a, 'b> {
155-
Bytes8 {
156-
bytes: bytes,
157-
}
158-
}
159-
160-
#[cfg(debug_assertions)]
161-
#[inline]
162-
fn new(bytes: &'a mut Bytes<'b>) -> Bytes8<'a, 'b> {
163-
Bytes8 {
164-
bytes,
165-
pos: 0,
166-
}
167-
}
168-
169-
#[cfg(not(debug_assertions))]
170-
#[inline]
171-
fn assert_pos(&mut self, _pos: usize) {
172-
}
173-
174-
#[cfg(debug_assertions)]
175-
#[inline]
176-
fn assert_pos(&mut self, pos: usize) {
177-
assert!(self.pos == pos);
178-
self.pos += 1;
179-
}
180-
}
181-
182-
#[cfg(test)]
183-
mod tests {
184-
use super::Bytes;
185-
186-
#[test]
187-
fn test_next_8_too_short() {
188-
// Start with 10 bytes.
189-
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
190-
let mut bytes = Bytes::new(&slice);
191-
// Skip 3 of them.
192-
unsafe { bytes.advance(3); }
193-
// There should be 7 left, not enough to call next_8.
194-
assert!(bytes.next_8().is_none());
195-
}
196-
197-
#[test]
198-
fn test_next_8_just_right() {
199-
// Start with 10 bytes.
200-
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
201-
let mut bytes = Bytes::new(&slice);
202-
// Skip 2 of them.
203-
unsafe { bytes.advance(2); }
204-
// There should be 8 left, just enough to call next_8.
205-
let ret = bytes.next_8();
206-
assert!(ret.is_some());
207-
let mut ret = ret.unwrap();
208-
// They should be the bytes starting with 2.
209-
assert_eq!(ret._0(), 2u8);
210-
assert_eq!(ret._1(), 3u8);
211-
assert_eq!(ret._2(), 4u8);
212-
assert_eq!(ret._3(), 5u8);
213-
assert_eq!(ret._4(), 6u8);
214-
assert_eq!(ret._5(), 7u8);
215-
assert_eq!(ret._6(), 8u8);
216-
assert_eq!(ret._7(), 9u8);
217-
}
218-
219-
#[test]
220-
fn test_next_8_extra() {
221-
// Start with 10 bytes.
222-
let slice = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8];
223-
let mut bytes = Bytes::new(&slice);
224-
// Skip 1 of them.
225-
unsafe { bytes.advance(1); }
226-
// There should be 9 left, more than enough to call next_8.
227-
let ret = bytes.next_8();
228-
assert!(ret.is_some());
229-
let mut ret = ret.unwrap();
230-
// They should be the bytes starting with 1.
231-
assert_eq!(ret._0(), 1u8);
232-
assert_eq!(ret._1(), 2u8);
233-
assert_eq!(ret._2(), 3u8);
234-
assert_eq!(ret._3(), 4u8);
235-
assert_eq!(ret._4(), 5u8);
236-
assert_eq!(ret._5(), 6u8);
237-
assert_eq!(ret._6(), 7u8);
238-
assert_eq!(ret._7(), 8u8);
239-
}
240-
}

src/lib.rs

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -478,14 +478,16 @@ impl<'h, 'b> Request<'h, 'b> {
478478
let orig_len = buf.len();
479479
let mut bytes = Bytes::new(buf);
480480
complete!(skip_empty_lines(&mut bytes));
481-
let method = match bytes.peek_4() {
482-
Some(b"GET ") => {
481+
const GET: [u8; 4] = *b"GET ";
482+
const POST: [u8; 4] = *b"POST";
483+
let method = match bytes.peek_n::<[u8; 4]>(4) {
484+
Some(GET) => {
483485
unsafe {
484486
bytes.advance_and_commit(4);
485487
}
486488
"GET"
487489
}
488-
Some(b"POST") if bytes.peek_ahead(4) == Some(b' ') => {
490+
Some(POST) if bytes.peek_ahead(4) == Some(b' ') => {
489491
unsafe {
490492
bytes.advance_and_commit(5);
491493
}
@@ -744,20 +746,13 @@ pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" };
744746

745747
#[inline]
746748
fn parse_version(bytes: &mut Bytes) -> Result<u8> {
747-
if let Some(mut eight) = bytes.next_8() {
748-
expect!(eight._0() => b'H' |? Err(Error::Version));
749-
expect!(eight._1() => b'T' |? Err(Error::Version));
750-
expect!(eight._2() => b'T' |? Err(Error::Version));
751-
expect!(eight._3() => b'P' |? Err(Error::Version));
752-
expect!(eight._4() => b'/' |? Err(Error::Version));
753-
expect!(eight._5() => b'1' |? Err(Error::Version));
754-
expect!(eight._6() => b'.' |? Err(Error::Version));
755-
let v = match eight._7() {
756-
b'0' => 0,
757-
b'1' => 1,
758-
_ => return Err(Error::Version)
759-
};
760-
return Ok(Status::Complete(v))
749+
if let Some(eight) = bytes.peek_n::<[u8; 8]>(8) {
750+
unsafe { bytes.advance(8); }
751+
return match &eight {
752+
b"HTTP/1.0" => Ok(Status::Complete(0)),
753+
b"HTTP/1.1" => Ok(Status::Complete(1)),
754+
_ => Err(Error::Version),
755+
}
761756
}
762757

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

11191114
'value_line: loop {
1120-
if let Some(mut bytes8) = bytes.next_8() {
1115+
if let Some(bytes8) = bytes.peek_n::<[u8; 8]>(8) {
11211116
macro_rules! check {
1122-
($bytes:ident, $i:ident) => ({
1123-
b = $bytes.$i();
1117+
($bytes:ident, $i:literal) => ({
1118+
b = $bytes[$i];
11241119
if !is_header_value_token(b) {
1120+
unsafe { bytes.advance($i + 1); }
11251121
break 'value_line;
11261122
}
11271123
});
11281124
}
11291125

1130-
check!(bytes8, _0);
1131-
check!(bytes8, _1);
1132-
check!(bytes8, _2);
1133-
check!(bytes8, _3);
1134-
check!(bytes8, _4);
1135-
check!(bytes8, _5);
1136-
check!(bytes8, _6);
1137-
check!(bytes8, _7);
1126+
check!(bytes8, 0);
1127+
check!(bytes8, 1);
1128+
check!(bytes8, 2);
1129+
check!(bytes8, 3);
1130+
check!(bytes8, 4);
1131+
check!(bytes8, 5);
1132+
check!(bytes8, 6);
1133+
check!(bytes8, 7);
1134+
unsafe { bytes.advance(8); }
11381135

11391136
continue 'value_line;
11401137
}

0 commit comments

Comments
 (0)