mirror of
https://github.com/rustdesk/rustdesk
synced 2026-04-21 13:27:19 +00:00
Critical fixes:
- Encoder single-frame benchmarks now alternate 8 distinct frames to
avoid rc_dropframe_thresh=25 dropping frames on identical input
(previous results measured Err("no valid frame"), not actual encoding)
- Added keyframe-only benchmark (keyframe_interval: Some(1)) for
worst-case encode cost measurement
- pipeline_encode: wrap write_to_bytes() in black_box to prevent LLVM
dead-code elimination of the serialization step
- pipeline_encode sequence: use write_to_bytes() instead of compute_size()
for consistency with single-frame benchmarks
Measurement fixes:
- mutex_contention: persistent threads + Barrier instead of
thread::spawn inside b.iter() (spawn overhead was dominating lock cost)
- mutex_contention: Throughput::Elements(4000) for 4×1000 ops
- video_queue: pre-allocate ArrayQueue and VideoFrame outside hot loop
- pipeline_decode: use .take() instead of .clone() on Union (matches
real code path which moves the VideoFrame)
New benchmarks:
- decode_raw: VP9 decode without YUV→RGB conversion (isolates codec cost)
- decode_alignment: align=1 vs align=64 (macOS texture rendering)
- encoder_cold_start: Encoder::new() cost (VP8/VP9/AV1)
- decoder_cold_start: Decoder::new() cost (VP8/VP9/AV1)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
320 lines
11 KiB
Rust
320 lines
11 KiB
Rust
mod common;
|
|
|
|
use common::make_i420;
|
|
use criterion::{
|
|
black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
|
};
|
|
use scrap::{
|
|
aom::{AomEncoder, AomEncoderConfig},
|
|
codec::{EncoderApi, EncoderCfg},
|
|
EncodeInput, VpxEncoder, VpxEncoderConfig, VpxVideoCodecId,
|
|
};
|
|
use std::time::Duration;
|
|
|
|
/// B. Video encode benchmarks.
|
|
///
|
|
/// Calls the real `EncoderApi::encode_to_message()` — the same function used
|
|
/// by video_service.rs handle_one_frame().
|
|
///
|
|
/// Single-frame benchmarks alternate between multiple distinct frames to avoid
|
|
/// the encoder's rate controller dropping frames on identical input
|
|
/// (rc_dropframe_thresh=25 causes Err("no valid frame") on static content).
|
|
|
|
const W: usize = 1920;
|
|
const H: usize = 1080;
|
|
const NUM_FRAMES: usize = 8;
|
|
|
|
/// Pre-generate a pool of distinct YUV frames for realistic encode benchmarks.
|
|
fn make_frame_pool(w: usize, h: usize, n: usize) -> Vec<Vec<u8>> {
|
|
(0..n).map(|i| make_i420(w, h, i * 37).0).collect()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Single-frame encode with varied input (VP8, VP9, AV1)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_vpx_encode_single(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("encode_single");
|
|
|
|
for codec in [VpxVideoCodecId::VP8, VpxVideoCodecId::VP9] {
|
|
let label = match codec {
|
|
VpxVideoCodecId::VP8 => "vp8_1080p",
|
|
VpxVideoCodecId::VP9 => "vp9_1080p",
|
|
};
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
codec,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
let pool = make_frame_pool(W, H, NUM_FRAMES);
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(BenchmarkId::from_parameter(label), &(), |b, _| {
|
|
let mut pts = 0i64;
|
|
b.iter(|| {
|
|
let yuv = &pool[pts as usize % pool.len()];
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, pts));
|
|
pts += 1;
|
|
});
|
|
});
|
|
}
|
|
|
|
// AV1
|
|
{
|
|
let cfg = EncoderCfg::AOM(AomEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = AomEncoder::new(cfg, false).unwrap();
|
|
let pool = make_frame_pool(W, H, NUM_FRAMES);
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(BenchmarkId::from_parameter("av1_1080p"), &(), |b, _| {
|
|
let mut pts = 0i64;
|
|
b.iter(|| {
|
|
let yuv = &pool[pts as usize % pool.len()];
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, pts));
|
|
pts += 1;
|
|
});
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Keyframe-only encode (worst case — every frame is a keyframe)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_vpx_encode_keyframe(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("encode_keyframe");
|
|
|
|
for codec in [VpxVideoCodecId::VP8, VpxVideoCodecId::VP9] {
|
|
let label = match codec {
|
|
VpxVideoCodecId::VP8 => "vp8_1080p",
|
|
VpxVideoCodecId::VP9 => "vp9_1080p",
|
|
};
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
codec,
|
|
keyframe_interval: Some(1), // force keyframe every frame
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
let pool = make_frame_pool(W, H, NUM_FRAMES);
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(BenchmarkId::from_parameter(label), &(), |b, _| {
|
|
let mut pts = 0i64;
|
|
b.iter(|| {
|
|
let yuv = &pool[pts as usize % pool.len()];
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, pts));
|
|
pts += 1;
|
|
});
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4K encode with varied input
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_encode_4k(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("encode_4k");
|
|
group.measurement_time(Duration::from_secs(15));
|
|
let (w4k, h4k) = (3840, 2160);
|
|
|
|
for codec in [VpxVideoCodecId::VP8, VpxVideoCodecId::VP9] {
|
|
let label = match codec {
|
|
VpxVideoCodecId::VP8 => "vp8",
|
|
VpxVideoCodecId::VP9 => "vp9",
|
|
};
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: w4k as _,
|
|
height: h4k as _,
|
|
quality: 1.0,
|
|
codec,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
let pool = make_frame_pool(w4k, h4k, 4); // fewer frames for 4K (memory)
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(BenchmarkId::from_parameter(label), &(), |b, _| {
|
|
let mut pts = 0i64;
|
|
b.iter(|| {
|
|
let yuv = &pool[pts as usize % pool.len()];
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, pts));
|
|
pts += 1;
|
|
});
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sequence encode: 100 static frames (simulates idle screen)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_vp9_encode_sequence_static(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("vp9_encode_seq_static");
|
|
group.sample_size(10);
|
|
group.measurement_time(Duration::from_secs(30));
|
|
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
codec: VpxVideoCodecId::VP9,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
let (yuv, _) = make_i420(W, H, 0);
|
|
|
|
group.throughput(Throughput::Elements(100));
|
|
group.bench_function(BenchmarkId::from_parameter("100frames_1080p"), |b| {
|
|
b.iter(|| {
|
|
for i in 0..100 {
|
|
let input = EncodeInput::YUV(black_box(&yuv));
|
|
drop(encoder.encode_to_message(input, i));
|
|
}
|
|
});
|
|
});
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Sequence encode: 100 varied frames (simulates scroll / movement)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_vp9_encode_sequence_movement(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("vp9_encode_seq_movement");
|
|
group.sample_size(10);
|
|
group.measurement_time(Duration::from_secs(30));
|
|
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
codec: VpxVideoCodecId::VP9,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
let frames: Vec<Vec<u8>> = (0..100).map(|i| make_i420(W, H, i * 5).0).collect();
|
|
|
|
group.throughput(Throughput::Elements(100));
|
|
group.bench_function(BenchmarkId::from_parameter("100frames_1080p"), |b| {
|
|
b.iter(|| {
|
|
for (i, yuv) in frames.iter().enumerate() {
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, i as i64));
|
|
}
|
|
});
|
|
});
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Quality ratio impact (VP9 1080p, varied input)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_vp9_encode_quality(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("vp9_encode_quality");
|
|
group.measurement_time(Duration::from_secs(10));
|
|
|
|
let qualities: &[(&str, f32)] = &[
|
|
("q0.5_speed", 0.5),
|
|
("q1.0_balanced", 1.0),
|
|
("q2.0_best", 2.0),
|
|
];
|
|
let pool = make_frame_pool(W, H, NUM_FRAMES);
|
|
|
|
for (label, quality) in qualities {
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: *quality,
|
|
codec: VpxVideoCodecId::VP9,
|
|
keyframe_interval: None,
|
|
});
|
|
let mut encoder = VpxEncoder::new(cfg, false).unwrap();
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(BenchmarkId::from_parameter(*label), &(), |b, _| {
|
|
let mut pts = 0i64;
|
|
b.iter(|| {
|
|
let yuv = &pool[pts as usize % pool.len()];
|
|
let input = EncodeInput::YUV(black_box(yuv));
|
|
drop(encoder.encode_to_message(input, pts));
|
|
pts += 1;
|
|
});
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cold-start: encoder creation cost (reconnection / codec switch)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bench_encoder_cold_start(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("encoder_cold_start");
|
|
|
|
for codec in [VpxVideoCodecId::VP8, VpxVideoCodecId::VP9] {
|
|
let label = match codec {
|
|
VpxVideoCodecId::VP8 => "vp8_1080p",
|
|
VpxVideoCodecId::VP9 => "vp9_1080p",
|
|
};
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_function(BenchmarkId::from_parameter(label), |b| {
|
|
b.iter(|| {
|
|
let cfg = EncoderCfg::VPX(VpxEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
codec,
|
|
keyframe_interval: None,
|
|
});
|
|
black_box(VpxEncoder::new(cfg, false).unwrap());
|
|
});
|
|
});
|
|
}
|
|
|
|
{
|
|
group.bench_function(BenchmarkId::from_parameter("av1_1080p"), |b| {
|
|
b.iter(|| {
|
|
let cfg = EncoderCfg::AOM(AomEncoderConfig {
|
|
width: W as _,
|
|
height: H as _,
|
|
quality: 1.0,
|
|
keyframe_interval: None,
|
|
});
|
|
black_box(AomEncoder::new(cfg, false).unwrap());
|
|
});
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
bench_vpx_encode_single,
|
|
bench_vpx_encode_keyframe,
|
|
bench_encode_4k,
|
|
bench_encoder_cold_start,
|
|
bench_vp9_encode_sequence_static,
|
|
bench_vp9_encode_sequence_movement,
|
|
bench_vp9_encode_quality,
|
|
);
|
|
criterion_main!(benches);
|