Skip to content

Commit

Permalink
Add: simple bench on CowContent vs BytesContent
Browse files Browse the repository at this point in the history
  • Loading branch information
kanarus committed Feb 19, 2024
1 parent 115ca66 commit 051004f
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 1 deletion.
3 changes: 2 additions & 1 deletion benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ authors = ["kanarus <[email protected]>"]
[dependencies]
ohkami = { version = "0.14.1", path = "../ohkami", features = ["rt_tokio", "DEBUG"] }
http = "1.0.0"
rustc-hash = "1.1"
rustc-hash = "1.1"
bytes = "1.5.0"
255 changes: 255 additions & 0 deletions benches/benches/content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#![feature(test)]
extern crate test;

use std::borrow::Cow;


struct CowContent(
Option<Cow<'static, [u8]>>,
);
impl CowContent {
#[inline] fn new(bytes: &[u8]) -> Self {
Self(Some(Cow::Owned(bytes.into())))
}
#[inline] fn write_to(&self, buf: &mut Vec<u8>) {
match &self.0 {
Some(Cow::Borrowed(slice)) => buf.extend_from_slice(slice),
Some(Cow::Owned(vector)) => buf.extend_from_slice(&*vector),
None => ()
}
}
}

struct BytesContent(
Option<::bytes::Bytes>,
);
impl BytesContent {
#[inline] fn new(bytes: &[u8]) -> Self {
Self(Some(::bytes::Bytes::copy_from_slice(bytes)))
}
#[inline] fn write_to(&self, buf: &mut Vec<u8>) {
match &self.0 {
Some(b) => buf.extend_from_slice(&b),
None => (),
}
}
}


fn small() -> Vec<u8> {
Vec::from(test::black_box("\
Bytes contains a vtable, which allows implementations of Bytes to define \
how sharing/cloning is implemented in detail. When Bytes::clone() is called, \
Bytes will call the vtable function for cloning the backing storage in order \
to share it behind between multiple Bytes instances.\
"))
}

fn large() -> Vec<u8> {
Vec::from(test::black_box(r#"\
[[lib.rs]]
Provides abstractions for working with bytes.
The `bytes` crate provides an efficient byte buffer structure
([`Bytes`]) and traits for working with buffer
implementations ([`Buf`], [`BufMut`]).
# `Bytes`
`Bytes` is an efficient container for storing and operating on contiguous
slices of memory. It is intended for use primarily in networking code, but
could have applications elsewhere as well.
`Bytes` values facilitate zero-copy network programming by allowing multiple
`Bytes` objects to point to the same underlying memory. This is managed by
using a reference count to track when the memory is no longer needed and can
be freed.
A `Bytes` handle can be created directly from an existing byte store (such as `&[u8]`
or `Vec<u8>`), but usually a `BytesMut` is used first and written to. For
example:
```rust
use bytes::{BytesMut, BufMut};
let mut buf = BytesMut::with_capacity(1024);
buf.put(&b"hello world"[..]);
buf.put_u16(1234);
let a = buf.split();
assert_eq!(a, b"hello world\x04\xD2"[..]);
buf.put(&b"goodbye world"[..]);
let b = buf.split();
assert_eq!(b, b"goodbye world"[..]);
assert_eq!(buf.capacity(), 998);
```
In the above example, only a single buffer of 1024 is allocated. The handles
`a` and `b` will share the underlying buffer and maintain indices tracking
the view into the buffer represented by the handle.
See the [struct docs](`Bytes`) for more details.
# `Buf`, `BufMut`
These two traits provide read and write access to buffers. The underlying
storage may or may not be in contiguous memory. For example, `Bytes` is a
buffer that guarantees contiguous memory, but a [rope] stores the bytes in
disjoint chunks. `Buf` and `BufMut` maintain cursors tracking the current
position in the underlying byte storage. When bytes are read or written, the
cursor is advanced.
[rope]: https://en.wikipedia.org/wiki/Rope_(data_structure)
## Relation with `Read` and `Write`
At first glance, it may seem that `Buf` and `BufMut` overlap in
functionality with [`std::io::Read`] and [`std::io::Write`]. However, they
serve different purposes. A buffer is the value that is provided as an
argument to `Read::read` and `Write::write`. `Read` and `Write` may then
perform a syscall, which has the potential of failing. Operations on `Buf`
and `BufMut` are infallible.
---
[[bytes.rs]]
A cheaply cloneable and sliceable chunk of contiguous memory.
Bytes is an efficient container for storing and operating on contiguous slices of memory. \
It is intended for use primarily in networking code, but could have applications elsewhere as well.
Bytes values facilitate zero-copy network programming by allowing multiple Bytes objects \
to point to the same underlying memory.
Bytes does not have a single implementation. It is an interface, whose exact behavior is implemented \
through dynamic dispatch in several underlying implementations of Bytes.
All Bytes implementations must fulfill the following requirements:
- They are cheaply cloneable and thereby shareable between an unlimited amount of components, \
for example by modifying a reference count.
- Instances can be sliced to refer to a subset of the original buffer.
## Memory layout
The Bytes struct itself is fairly small, limited to 4 usize fields used to track information \
about which segment of the underlying memory the Bytes handle has access to.
Bytes keeps both a pointer to the shared state containing the full memory slice \
and a pointer to the start of the region visible by the handle. Bytes also tracks the length of its view \
into the memory.
## Sharing
Bytes contains a vtable, which allows implementations of Bytes to define \
how sharing/cloning is implemented in detail. When Bytes::clone() is called, \
Bytes will call the vtable function for cloning the backing storage in order \
to share it behind between multiple Bytes instances.
For Bytes implementations which refer to constant memory (e.g. created via Bytes::from_static()) \
the cloning implementation will be a no-op.
For Bytes implementations which point to a reference counted shared storage (e.g. an Arc<[u8]>), \
sharing will be implemented by increasing the reference count.
Due to this mechanism, multiple Bytes instances may point to the same shared memory region. \
Each Bytes instance can point to different sections within that memory region, and \
Bytes instances may or may not have overlapping views into the memory.
The following diagram visualizes a scenario where 2 Bytes instances make use of an Arc-based backing storage, \
and provide access to different views:
```text
Arc ptrs ┌─────────┐
________________________ / │ Bytes 2 │
/ └─────────┘
/ ┌───────────┐ | |
|_________/ │ Bytes 1 │ | |
| └───────────┘ | |
| | | ___/ data | tail
| data | tail |/ |
v v v v
┌─────┬─────┬───────────┬───────────────┬─────┐
│ Arc │ │ │ │ │
└─────┴─────┴───────────┴───────────────┴─────┘
```
"#))
}


#[bench] fn create_small_cow(b: &mut test::Bencher) {
let data = small();

b.iter(|| {
let _c = CowContent::new(&data);
})
}
#[bench] fn create_large_cow(b: &mut test::Bencher) {
let data = large();

b.iter(|| {
let _c = CowContent::new(&data);
})
}

#[bench] fn create_small_bytes(b: &mut test::Bencher) {
let data = small();

b.iter(|| {
let _c = BytesContent::new(&data);
})
}
#[bench] fn create_large_bytes(b: &mut test::Bencher) {
let data = large();

b.iter(|| {
let _c = BytesContent::new(&data);
})
}


#[bench] fn write_small_cow(b: &mut test::Bencher) {
let mut buf = Vec::new();

let data = small();
let c = CowContent::new(&data);

b.iter(|| {
c.write_to(&mut buf);
})
}
#[bench] fn write_large_cow(b: &mut test::Bencher) {
let mut buf = Vec::new();

let data = large();
let c = CowContent::new(&data);

b.iter(|| {
c.write_to(&mut buf);
})
}

#[bench] fn write_small_bytes(b: &mut test::Bencher) {
let mut buf = Vec::new();

let data = small();
let c = BytesContent::new(&data);

b.iter(|| {
c.write_to(&mut buf);
})
}
#[bench] fn write_large_bytes(b: &mut test::Bencher) {
let mut buf = Vec::new();

let data = large();
let c = BytesContent::new(&data);

b.iter(|| {
c.write_to(&mut buf);
})
}

0 comments on commit 051004f

Please sign in to comment.