mirror of
https://github.com/itsjunetime/tdf.git
synced 2026-06-01 23:51:46 -04:00
- Add profiling with --profile-time
- Update ratatui_image to get improvements from parallelization and removing unnecessary hashing - Add benchmarks for only converting pages (not rendering) - Add option to define number of pages to prerender on converter
This commit is contained in:
+145
-5
@@ -1,7 +1,21 @@
|
||||
mod utils;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use utils::{render_doc, render_first_page};
|
||||
use std::{
|
||||
hint::black_box,
|
||||
path::Path,
|
||||
time::{SystemTime, UNIX_EPOCH}
|
||||
};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, profiler::Profiler, BenchmarkId, Criterion};
|
||||
use futures_util::StreamExt;
|
||||
use tdf::{
|
||||
converter::{ConvertedPage, ConverterMsg},
|
||||
renderer::{fill_default, PageInfo, RenderInfo}
|
||||
};
|
||||
use utils::{
|
||||
handle_converter_msg, handle_renderer_msg, render_doc, start_all_rendering,
|
||||
start_converting_loop, start_rendering_loop, RenderState
|
||||
};
|
||||
|
||||
const FILES: [&str; 2] = [
|
||||
"benches/example_dictionary.pdf",
|
||||
@@ -18,7 +32,7 @@ fn render_full(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn render_to_first_page(c: &mut Criterion) {
|
||||
for file in FILES {
|
||||
for file in &FILES[..1] {
|
||||
c.bench_with_input(
|
||||
BenchmarkId::new("render_first_page", file),
|
||||
&file,
|
||||
@@ -30,9 +44,135 @@ fn render_to_first_page(c: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn only_converting(c: &mut Criterion) {
|
||||
for file in FILES {
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
let all_rendered = runtime.block_on(render_all_files(file));
|
||||
|
||||
c.bench_with_input(
|
||||
BenchmarkId::new("only_converting", file),
|
||||
&(all_rendered, file),
|
||||
|b, (rendered, _)| {
|
||||
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||
.iter(|| convert_all_files(rendered.clone()))
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn render_first_page(path: impl AsRef<Path>) {
|
||||
let RenderState {
|
||||
mut from_render_rx,
|
||||
mut from_converter_rx,
|
||||
mut pages,
|
||||
mut to_converter_tx,
|
||||
to_render_tx
|
||||
} = start_all_rendering(path);
|
||||
|
||||
// we only want to render until the first page is ready to be printed
|
||||
while pages.is_empty() {
|
||||
tokio::select! {
|
||||
Some(renderer_msg) = from_render_rx.next() => {
|
||||
handle_renderer_msg(renderer_msg, &mut pages, &mut to_converter_tx);
|
||||
},
|
||||
Some(converter_msg) = from_converter_rx.next() => {
|
||||
handle_converter_msg(converter_msg, &mut pages, &mut to_converter_tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
black_box(pages);
|
||||
// we want to make sure this is kept around until the end of this function, or else the other
|
||||
// thread will see that this is disconnected and think that we're done communicating with them
|
||||
drop(to_render_tx);
|
||||
}
|
||||
|
||||
async fn render_all_files(path: &'static str) -> Vec<PageInfo> {
|
||||
let (mut from_render_rx, to_render_tx) = start_rendering_loop(path);
|
||||
let mut pages = Vec::<Option<PageInfo>>::new();
|
||||
|
||||
while let Some(info) = from_render_rx.next().await {
|
||||
match info.expect("Renderer ran into an error while rendering") {
|
||||
RenderInfo::NumPages(num) => fill_default(&mut pages, num),
|
||||
RenderInfo::Page(page) => {
|
||||
let num = page.page;
|
||||
pages[num] = Some(page);
|
||||
}
|
||||
};
|
||||
|
||||
if pages.iter().all(|p| p.is_some()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drop(to_render_tx);
|
||||
pages.into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
async fn convert_all_files(files: Vec<PageInfo>) {
|
||||
let num_files = files.len();
|
||||
let (mut from_converter_rx, to_converter_tx) = start_converting_loop(num_files);
|
||||
|
||||
to_converter_tx
|
||||
.send(ConverterMsg::NumPages(num_files))
|
||||
.unwrap();
|
||||
|
||||
let mut converted = Vec::<Option<ConvertedPage>>::new();
|
||||
fill_default(&mut converted, num_files);
|
||||
|
||||
for page in files {
|
||||
to_converter_tx.send(ConverterMsg::AddImg(page)).unwrap();
|
||||
|
||||
if !from_converter_rx.is_empty() {
|
||||
let page = from_converter_rx
|
||||
.next()
|
||||
.await
|
||||
.expect("Converter ended stream before expected")
|
||||
.expect("Converter ran into an error while converting page");
|
||||
|
||||
let num = page.num;
|
||||
converted[num] = Some(page);
|
||||
}
|
||||
}
|
||||
|
||||
while converted.iter().any(|p| p.is_none()) {
|
||||
let page = from_converter_rx
|
||||
.next()
|
||||
.await
|
||||
.expect("Converted ended stream before expected")
|
||||
.expect("Converted ran into an error while converting page");
|
||||
|
||||
let num = page.num;
|
||||
converted[num] = Some(page);
|
||||
}
|
||||
|
||||
drop(to_converter_tx);
|
||||
black_box(converted);
|
||||
}
|
||||
|
||||
struct CpuProfiler;
|
||||
|
||||
impl Profiler for CpuProfiler {
|
||||
fn start_profiling(&mut self, benchmark_id: &str, _: &std::path::Path) {
|
||||
let file = format!(
|
||||
"./{}-{}.profile",
|
||||
benchmark_id.replace("/", "-"),
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
);
|
||||
|
||||
cpuprofiler::PROFILER.lock().unwrap().start(file).unwrap()
|
||||
}
|
||||
fn stop_profiling(&mut self, _: &str, _: &std::path::Path) {
|
||||
cpuprofiler::PROFILER.lock().unwrap().stop().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
name = benches;
|
||||
config = Criterion::default().sample_size(40);
|
||||
targets = render_full, render_to_first_page
|
||||
config = Criterion::default().sample_size(40).with_profiler(CpuProfiler);
|
||||
targets = render_full, render_to_first_page, only_converting
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
+48
-50
@@ -10,7 +10,7 @@ use tdf::{
|
||||
renderer::{fill_default, start_rendering, RenderError, RenderInfo, RenderNotif}
|
||||
};
|
||||
|
||||
fn handle_renderer_msg(
|
||||
pub fn handle_renderer_msg(
|
||||
msg: Result<RenderInfo, RenderError>,
|
||||
pages: &mut Vec<Option<ConvertedPage>>,
|
||||
to_converter_tx: &mut Sender<tdf::converter::ConverterMsg>
|
||||
@@ -25,7 +25,7 @@ fn handle_renderer_msg(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_converter_msg(
|
||||
pub fn handle_converter_msg(
|
||||
msg: Result<ConvertedPage, RenderError>,
|
||||
pages: &mut [Option<ConvertedPage>],
|
||||
to_converter_tx: &mut Sender<ConverterMsg>
|
||||
@@ -44,43 +44,39 @@ fn handle_converter_msg(
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct RenderState {
|
||||
from_render_rx: RecvStream<'static, Result<RenderInfo, RenderError>>,
|
||||
from_converter_rx: RecvStream<'static, Result<ConvertedPage, RenderError>>,
|
||||
pages: Vec<Option<ConvertedPage>>,
|
||||
to_converter_tx: Sender<ConverterMsg>,
|
||||
to_render_tx: Sender<RenderNotif>
|
||||
pub struct RenderState {
|
||||
pub from_render_rx: RecvStream<'static, Result<RenderInfo, RenderError>>,
|
||||
pub from_converter_rx: RecvStream<'static, Result<ConvertedPage, RenderError>>,
|
||||
pub pages: Vec<Option<ConvertedPage>>,
|
||||
pub to_converter_tx: Sender<ConverterMsg>,
|
||||
pub to_render_tx: Sender<RenderNotif>
|
||||
}
|
||||
|
||||
fn start_all_rendering(path: impl AsRef<Path>) -> RenderState {
|
||||
const FONT_SIZE: (u16, u16) = (8, 14);
|
||||
|
||||
pub fn start_rendering_loop(
|
||||
path: impl AsRef<Path>
|
||||
) -> (
|
||||
RecvStream<'static, Result<RenderInfo, RenderError>>,
|
||||
Sender<RenderNotif>
|
||||
) {
|
||||
let pathbuf = path.as_ref().canonicalize().unwrap();
|
||||
let str_path = format!("file://{}", pathbuf.into_os_string().to_string_lossy());
|
||||
|
||||
let (to_render_tx, from_main_rx) = unbounded();
|
||||
let (to_main_tx, from_render_rx) = unbounded();
|
||||
|
||||
let font_size = (8, 14);
|
||||
let (columns, rows) = (60, 180);
|
||||
|
||||
let size = WindowSize {
|
||||
columns,
|
||||
rows,
|
||||
height: rows * font_size.1,
|
||||
width: columns * font_size.0
|
||||
height: rows * FONT_SIZE.1,
|
||||
width: columns * FONT_SIZE.0
|
||||
};
|
||||
|
||||
std::thread::spawn(move || start_rendering(str_path, to_main_tx, from_main_rx, size));
|
||||
|
||||
let (to_converter_tx, from_main_rx) = unbounded();
|
||||
let (to_main_tx, from_converter_rx) = unbounded();
|
||||
|
||||
let mut picker = Picker::new(font_size);
|
||||
picker.protocol_type = ProtocolType::Kitty;
|
||||
|
||||
tokio::spawn(run_conversion_loop(to_main_tx, from_main_rx, picker));
|
||||
|
||||
let pages: Vec<Option<ConvertedPage>> = Vec::new();
|
||||
|
||||
let main_area = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -90,7 +86,37 @@ fn start_all_rendering(path: impl AsRef<Path>) -> RenderState {
|
||||
to_render_tx.send(RenderNotif::Area(main_area)).unwrap();
|
||||
|
||||
let from_render_rx = from_render_rx.into_stream();
|
||||
(from_render_rx, to_render_tx)
|
||||
}
|
||||
|
||||
pub fn start_converting_loop(
|
||||
prerender: usize
|
||||
) -> (
|
||||
RecvStream<'static, Result<ConvertedPage, RenderError>>,
|
||||
Sender<ConverterMsg>
|
||||
) {
|
||||
let (to_converter_tx, from_main_rx) = unbounded();
|
||||
let (to_main_tx, from_converter_rx) = unbounded();
|
||||
|
||||
let mut picker = Picker::new(FONT_SIZE);
|
||||
picker.protocol_type = ProtocolType::Kitty;
|
||||
|
||||
tokio::spawn(run_conversion_loop(
|
||||
to_main_tx,
|
||||
from_main_rx,
|
||||
picker,
|
||||
prerender
|
||||
));
|
||||
|
||||
let from_converter_rx = from_converter_rx.into_stream();
|
||||
(from_converter_rx, to_converter_tx)
|
||||
}
|
||||
|
||||
pub fn start_all_rendering(path: impl AsRef<Path>) -> RenderState {
|
||||
let (from_render_rx, to_render_tx) = start_rendering_loop(path);
|
||||
let (from_converter_rx, to_converter_tx) = start_converting_loop(20);
|
||||
|
||||
let pages: Vec<Option<ConvertedPage>> = Vec::new();
|
||||
|
||||
RenderState {
|
||||
from_render_rx,
|
||||
@@ -126,31 +152,3 @@ pub async fn render_doc(path: impl AsRef<Path>) {
|
||||
// thread will see that this is disconnected and think that we're done communicating with them
|
||||
drop(to_render_tx);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn render_first_page(path: impl AsRef<Path>) {
|
||||
let RenderState {
|
||||
mut from_render_rx,
|
||||
mut from_converter_rx,
|
||||
mut pages,
|
||||
mut to_converter_tx,
|
||||
to_render_tx
|
||||
} = start_all_rendering(path);
|
||||
|
||||
// we only want to render until the first page is ready to be printed
|
||||
while pages.is_empty() {
|
||||
tokio::select! {
|
||||
Some(renderer_msg) = from_render_rx.next() => {
|
||||
handle_renderer_msg(renderer_msg, &mut pages, &mut to_converter_tx);
|
||||
},
|
||||
Some(converter_msg) = from_converter_rx.next() => {
|
||||
handle_converter_msg(converter_msg, &mut pages, &mut to_converter_tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
black_box(pages);
|
||||
// we want to make sure this is kept around until the end of this function, or else the other
|
||||
// thread will see that this is disconnected and think that we're done communicating with them
|
||||
drop(to_render_tx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user