forked from esrlabs/chipmunk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Implementing benchmark for the producer cycle similar to how it's used with the sessions in Chipmunk. * Benchmarks still suffers from noise form the async runtime, but this noise is reduced as possible with variety of configuration and tricks * Remove async-trait from mocks after rebase from master
- Loading branch information
1 parent
a996912
commit 747b5fe
Showing
4 changed files
with
227 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 54 additions & 33 deletions
87
application/apps/indexer/sources/benches/mocks/mock_source.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,79 @@ | ||
use std::iter; | ||
|
||
use async_trait::async_trait; | ||
use sources::{ByteSource, ReloadInfo}; | ||
use criterion::black_box; | ||
use sources::ByteSource; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct MockByteSource { | ||
/// Represent the bytes that will be repeated to fill the internal buffer | ||
data_sample: u8, | ||
/// Sets how many bytes will be loaded into the internal buffer on each | ||
/// [`ByteSource::reload()`] call. | ||
load_amount: usize, | ||
/// The internal buffer | ||
buffer: Vec<u8>, | ||
} | ||
pub struct MockByteSource {} | ||
|
||
impl MockByteSource { | ||
/// Creates a new instant of [`MockByteSource`] | ||
/// | ||
/// * `data_sample`: Represent the bytes that will be repeated to fill the internal buffer | ||
/// * `load_amount`: Sets how many bytes will be loaded into the internal buffer on | ||
/// each [`ByteSource::reload()`] call. | ||
pub fn new(data_sample: u8, load_amount: usize) -> Self { | ||
Self { | ||
data_sample, | ||
load_amount, | ||
buffer: Vec::new(), | ||
} | ||
pub fn new() -> Self { | ||
Self {} | ||
} | ||
} | ||
|
||
#[async_trait] | ||
// NOTE: Methods within trait implementation have inner non-async function that should never be | ||
// inline and the trait method should be always inline. This remove unnecessary `Future::poll()` | ||
// calls from the runtime to reduce its noise. | ||
|
||
impl ByteSource for MockByteSource { | ||
#[inline(always)] | ||
fn consume(&mut self, offset: usize) { | ||
self.buffer | ||
.truncate(self.buffer.len().checked_sub(offset).unwrap()) | ||
#[inline(never)] | ||
fn inner(offset: usize) { | ||
const ZERO: usize = 0; | ||
|
||
if offset == black_box(ZERO) { | ||
println!("Random message to make sure the compiler won't optimize this"); | ||
} | ||
} | ||
|
||
inner(offset); | ||
} | ||
|
||
#[inline(always)] | ||
fn current_slice(&self) -> &[u8] { | ||
&self.buffer | ||
#[inline(never)] | ||
fn inner(_phantom: &MockByteSource) -> &[u8] { | ||
black_box({ | ||
const BYTES: [u8; 3] = [b'a', b's', b'a']; | ||
const REF: &[u8] = &BYTES; | ||
|
||
REF | ||
}) | ||
} | ||
|
||
inner(self) | ||
} | ||
|
||
#[inline(always)] | ||
fn len(&self) -> usize { | ||
self.buffer.len() | ||
#[inline(never)] | ||
fn inner() -> usize { | ||
const LEN: usize = 3; | ||
|
||
black_box(LEN) | ||
} | ||
|
||
inner() | ||
} | ||
|
||
#[inline(always)] | ||
async fn reload( | ||
&mut self, | ||
_filter: Option<&sources::SourceFilter>, | ||
) -> Result<Option<sources::ReloadInfo>, sources::Error> { | ||
self.buffer | ||
.extend(iter::repeat(self.data_sample).take(self.load_amount)); | ||
#[inline(never)] | ||
fn inner() -> Result<Option<sources::ReloadInfo>, sources::Error> { | ||
const AA: Result<Option<sources::ReloadInfo>, sources::Error> = | ||
Ok(Some(sources::ReloadInfo { | ||
available_bytes: 5, | ||
newly_loaded_bytes: 5, | ||
skipped_bytes: 0, | ||
last_known_ts: None, | ||
})); | ||
|
||
let info = ReloadInfo::new(self.load_amount, self.len(), 0, None); | ||
black_box(AA) | ||
} | ||
|
||
Ok(Some(info)) | ||
inner() | ||
} | ||
} |
109 changes: 97 additions & 12 deletions
109
application/apps/indexer/sources/benches/producer_benchmarks.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,106 @@ | ||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; | ||
use std::time::Duration; | ||
|
||
async fn produce(val: usize, val_2: usize) { | ||
//TODO AAZ: | ||
assert_eq!(val, val_2); | ||
} | ||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; | ||
use futures::StreamExt; | ||
use mocks::{mock_parser::MockParser, mock_source::MockByteSource}; | ||
use parsers::{LogMessage, MessageStreamItem}; | ||
use sources::producer::MessageProducer; | ||
|
||
mod mocks; | ||
|
||
/// Runs Benchmarks replicating the producer loop within Chipmunk sessions, using mocks for | ||
/// [`parsers::Parser`] and [`sources::ByteSource`] to ensure that the measurements is for the | ||
/// producer loop only. | ||
/// | ||
/// NOTE: This benchmark suffers unfortunately from a lot of noise because we are running it with | ||
/// asynchronous runtime. This test is configured to reduce this amount of noise as possible, | ||
/// However it would be better to run it multiple time for double checking. | ||
fn producer_benchmark(c: &mut Criterion) { | ||
let val = 1024; | ||
let max_parse_calls = 50000; | ||
|
||
c.bench_with_input( | ||
BenchmarkId::new("run_producer", max_parse_calls), | ||
&(max_parse_calls), | ||
|bencher, &max| { | ||
bencher | ||
// It's important to spawn a new runtime on each run to ensure to reduce the | ||
// potential noise produced from one runtime created at the start of all benchmarks | ||
// only. | ||
.to_async(tokio::runtime::Runtime::new().unwrap()) | ||
.iter_batched( | ||
|| { | ||
// Exclude initiation time from benchmarks. | ||
let parser = MockParser::new(max); | ||
let byte_source = MockByteSource::new(); | ||
let producer = MessageProducer::new(parser, byte_source, black_box(None)); | ||
|
||
producer | ||
}, | ||
|producer| run_producer(producer), | ||
criterion::BatchSize::SmallInput, | ||
) | ||
}, | ||
); | ||
} | ||
|
||
/// Creates a message producer from the given arguments, then creates a stream of it and consumes | ||
/// it, replicating the producer loop inside Chipmunk sessions | ||
async fn run_producer<P, B, T>(mut producer: MessageProducer<T, P, B>) | ||
where | ||
P: parsers::Parser<T>, | ||
B: sources::ByteSource, | ||
T: LogMessage, | ||
{ | ||
let s = producer.as_stream(); | ||
// | ||
// using `tokio::pin!()` provided more stable (and faster) benchmarks than | ||
// `futures::pin_mut!()` | ||
tokio::pin!(s); | ||
|
||
while let Some((_, i)) = s.next().await { | ||
match i { | ||
MessageStreamItem::Item(item) => match item { | ||
parsers::ParseYield::Message(msg) => { | ||
if msg.to_string().is_empty() { | ||
println!("This should never be printed") | ||
} | ||
} | ||
parsers::ParseYield::Attachment(att) => { | ||
if att.size > 10 { | ||
println!("This should never be printed") | ||
} | ||
} | ||
parsers::ParseYield::MessageAndAttachment((msg, att)) => { | ||
if msg.to_string().is_empty() || att.size > 10 { | ||
println!("This should never be printed") | ||
} | ||
} | ||
}, | ||
MessageStreamItem::Skipped => { | ||
println!("This should never be printed") | ||
} | ||
MessageStreamItem::Incomplete => { | ||
println!("This should never be printed") | ||
} | ||
MessageStreamItem::Empty => println!("This should never be printed"), | ||
MessageStreamItem::Done => break, | ||
} | ||
} | ||
} | ||
|
||
c.bench_with_input(BenchmarkId::new("producer", val), &val, |bencher, &v| { | ||
bencher | ||
.to_async(tokio::runtime::Runtime::new().unwrap()) | ||
.iter(|| produce(v, v)); | ||
}); | ||
criterion_group! { | ||
name = benches; | ||
config = Criterion::default() | ||
// Warm up time is very important here because multiple async runtimes will be spawn in | ||
// that time which make the next ones to spawn more stable. | ||
.warm_up_time(Duration::from_secs(10)) | ||
// Measurement time and sample sized to role out noise in the measurements as possible. | ||
.measurement_time(Duration::from_secs(20)) | ||
.sample_size(200) | ||
// These two values help to reduce the noise level in the results. | ||
.significance_level(0.01) | ||
.noise_threshold(0.03); | ||
targets = producer_benchmark | ||
} | ||
|
||
criterion_group!(benches, producer_benchmark); | ||
criterion_main!(benches); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters