Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions sv2/binary-sv2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,9 @@ impl GetSize for Vec<u8> {
}
}

// Only needed for implement encodable for Frame never called
impl From<Vec<u8>> for EncodableField<'_> {
fn from(_v: Vec<u8>) -> Self {
unreachable!()
fn from(v: Vec<u8>) -> Self {
EncodableField::Struct(v.into_iter().map(Into::into).collect())
}
}

Expand Down
442 changes: 442 additions & 0 deletions sv2/framing-sv2/BENCHES.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion sv2/framing-sv2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ buffer_sv2 = { path = "../buffer-sv2", optional=true, version = "^3.0.0" }
noise_sv2 = { path = "../noise-sv2", version = "^1.0.0" }

[dev-dependencies]
noise_sv2 = { path = "../noise-sv2", version = "^1.0.0" }
criterion = { version = "0.3.0", features = ["html_reports"] }
rand = "0.8.3"
secp256k1 = { version = "0.28.2", default-features = false, features =["alloc","rand","rand-std"] }
rayon-core = "=1.12.1"

[features]
with_buffer_pool = ["binary_sv2/with_buffer_pool", "buffer_sv2"]

[package.metadata.docs.rs]
features = ["with_buffer_pool"]


[[bench]]
name = "framing"
harness = false
19 changes: 18 additions & 1 deletion sv2/framing-sv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,21 @@ This crate can be built with the following feature flags:
This crate provides an example demonstrating how to serialize and deserialize Sv2 message frames:

1. **[Sv2 Frame](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/framing-sv2/examples/sv2_frame.rs)**:
Constructs, serializes, and deserialize a regular Sv2 message frame (`Sv2Frame`).
Constructs, serializes, and deserialize a regular Sv2 message frame (`Sv2Frame`).

### Benches

This crate includes Criterion benchmarks under `benches/` covering:

* Framing performance (`framing.rs`)

Benchmarks are intended for regression tracking and relative comparison, not absolute performance claims.

Example benchmark output and methodology are documented in [BENCHES.md](./BENCHES.md).

Run them with:

```bash
cargo bench
cargo bench --features with_buffer_pool
```
146 changes: 146 additions & 0 deletions sv2/framing-sv2/benches/framing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use binary_sv2::{self, Serialize};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use framing_sv2::{framing::Sv2Frame, header::Header};

#[cfg(not(feature = "with_buffer_pool"))]
type Slice = Vec<u8>;

#[cfg(feature = "with_buffer_pool")]
type Slice = buffer_sv2::Slice;

#[cfg(feature = "with_buffer_pool")]
const BACKEND: &str = "buffer_pool";

#[cfg(not(feature = "with_buffer_pool"))]
const BACKEND: &str = "vec";

const PAYLOAD_SIZES: &[usize] = &[64, 1024, 16 * 1024, 60 * 1024];

fn payload(size: usize) -> Vec<u8> {
let len = size as u32;
let mut ve = Vec::with_capacity(6 + size);

ve.push(0);
ve.push(0);
ve.push(0);

ve.push((len & 0xFF) as u8);
ve.push(((len >> 8) & 0xFF) as u8);
ve.push(((len >> 16) & 0xFF) as u8);

ve.extend(std::iter::repeat(2u8).take(size));

ve
}

#[derive(Serialize, Clone)]
struct Tester {
_a: Vec<u8>,
}

fn tester(size: usize) -> Tester {
Tester {
_a: vec![2u8; size],
Copy link
Collaborator

Choose a reason for hiding this comment

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

shoudn't this be using payload()?

Suggested change
_a: vec![2u8; size],
_a: payload(size),

}
}

fn bench_from_message(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::from_message::{BACKEND}"));

for &size in PAYLOAD_SIZES {
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
b.iter(|| {
Sv2Frame::<Vec<u8>, Slice>::from_message(black_box(payload(size)), 1, 0, false)
.unwrap()
})
});
}

group.finish();
}

fn bench_serialize_fresh(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::serialize_fresh::{BACKEND}"));

for &size in PAYLOAD_SIZES {
let frame = Sv2Frame::<Tester, Slice>::from_message(tester(size), 1, 0, false).unwrap();

let mut buf = vec![0u8; frame.encoded_length()];

group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| {
frame.clone().serialize(&mut buf).unwrap();
})
});
}

group.finish();
}

fn bench_serialize_fast(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::serialize_fast::{BACKEND}"));

for &size in PAYLOAD_SIZES {
let frame = Sv2Frame::<Tester, Slice>::from_message(tester(size), 1, 0, false).unwrap();

let mut buf = vec![0u8; frame.encoded_length()];
frame.clone().serialize(&mut buf).unwrap();

group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| {
frame.clone().serialize(black_box(&mut buf)).unwrap();
})
});
}

group.finish();
}

fn bench_from_bytes(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::from_bytes::{BACKEND}"));

for &size in PAYLOAD_SIZES {
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| Sv2Frame::<Vec<u8>, _>::from_bytes(black_box(payload(size))).unwrap())
Copy link
Collaborator

Choose a reason for hiding this comment

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

aren't we also computing the time for the payload() to create the vec? Shoudn't we compute the payload()outside of the iter()?

});
}

group.finish();
}

fn bench_size_hint(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::size_hint::{BACKEND}"));

for &size in PAYLOAD_SIZES {
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| black_box(Sv2Frame::<Vec<u8>, Slice>::size_hint(&payload(size))))
});
}

group.finish();
}

fn bench_encrypted_len(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("sv2frame::encrypted_len::{BACKEND}"));

for &size in PAYLOAD_SIZES {
let header = Header::from_bytes(&payload(size)).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
b.iter(|| black_box(header.encrypted_len()))
});
}

group.finish();
}

criterion_group!(
framing,
bench_from_message,
bench_serialize_fresh,
bench_serialize_fast,
bench_from_bytes,
bench_size_hint,
bench_encrypted_len
);

criterion_main!(framing);
11 changes: 5 additions & 6 deletions sv2/framing-sv2/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,13 @@ impl Header {
///
/// The calculated length includes the full payload length and any additional space required
/// for the MACs.
#[allow(clippy::manual_div_ceil)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not use div_ceil?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

on my machine div_ceil was quite slow compared to current version.

pub fn encrypted_len(&self) -> usize {
let len = self.len();
let mut chunks = len / (SV2_FRAME_CHUNK_SIZE - AEAD_MAC_LEN);
if len % (SV2_FRAME_CHUNK_SIZE - AEAD_MAC_LEN) != 0 {
chunks += 1;
}
let mac_len = chunks * AEAD_MAC_LEN;
len + mac_len
let payload_per_chunk = SV2_FRAME_CHUNK_SIZE - AEAD_MAC_LEN;

let chunks = (len + payload_per_chunk - 1) / payload_per_chunk;
len + chunks * AEAD_MAC_LEN
}
}

Expand Down
Loading