From eed3196ba736a6a46ae5e5fa2eb05e3aca717bbc Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sun, 11 Oct 2026 14:52:39 +0200 Subject: [PATCH] #1039: count HEVC block statistics without branches Problem: after each picture the decoder classified every 4x4 block for DecodeStats with an if / else-if chain: about 130 000 unpredictable branches per 1080p picture, 1.5-2 % of single-thread decoding time. Fix: `block_counts` sums flag comparisons in one branch-free pass (inter = coded - intra - skip), with the same results. Tests: a unit test compares it with the if / else-if chain over every flag combination; identical decoded YUV on the five bench streams. Co-Authored-By: Claude Opus 5.5 --- crates/hevc/src/decoder.rs | 50 ++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/hevc/src/decoder.rs b/crates/hevc/src/decoder.rs index dd1cbd2b..97a0fb7f 100644 --- a/crates/hevc/src/decoder.rs +++ b/crates/hevc/src/decoder.rs @@ -10,7 +10,7 @@ use crate::error::{Error, Result, ensure, invalid}; use crate::params::{Layout, Pps, Sps, Vps}; use crate::picture::{Frame, FrameRef, Sample, stores_u8}; use crate::slice::{NalHeader, SliceHeader, nal_type}; -use crate::slicedec::{F_INTRA, F_SKIP, PicState, SliceDecoder, SliceJob}; +use crate::slicedec::{Blk, F_INTRA, F_SKIP, PicState, SliceDecoder, SliceJob}; use crate::{ColorInfo, Picture}; use filmcraft_bitstream::{annexb_nals, length_prefixed_nals, unescape_rbsp}; use std::collections::VecDeque; @@ -559,21 +559,28 @@ fn run_job_as( }; if let Ok(mut s) = shared.stats.lock() { s.pictures += 1; - for b in &pic.blk { - if b.flags & F_INTRA != 0 { - s.intra_4x4_blocks += 1; - } else if b.flags & F_SKIP != 0 { - s.skip_4x4_blocks += 1; - } else if b.flags != 0 { - s.inter_4x4_blocks += 1; - } - } + let [intra, skip, inter] = block_counts(&pic.blk); + s.intra_4x4_blocks += intra; + s.skip_4x4_blocks += skip; + s.inter_4x4_blocks += inter; } if let (Some(e), Ok(mut slot)) = (err, shared.error.lock()) { slot.get_or_insert(e); } } +/// Intra, skipped and other inter 4x4 blocks of a picture, branch-free (one pass over every +/// block): intra blocks have F_INTRA, skipped ones F_SKIP without it, inter ones any other flags. +fn block_counts(blk: &[Blk]) -> [u64; 3] { + let (mut intra, mut skip, mut coded) = (0u64, 0u64, 0u64); + for b in blk { + intra += (b.flags & F_INTRA != 0) as u64; + skip += (b.flags & (F_INTRA | F_SKIP) == F_SKIP) as u64; + coded += (b.flags != 0) as u64; + } + [intra, skip, coded.saturating_sub(intra + skip)] +} + fn output_meta(sps: &Sps, pts: i64, key: bool, draft: bool) -> OutputMeta { let vui = sps.vui.clone().unwrap_or_default(); OutputMeta { @@ -620,3 +627,26 @@ fn make_picture(o: &Output) -> Picture { draft: o.meta.draft, } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The branch-free block counts classify every flag combination as the if / else-if chain did. + #[test] + fn block_counts_match_the_classification_chain() { + let blk: Vec = (0..=255u8).flat_map(|f| [Blk { flags: f, ..Blk::default() }; 3]).collect(); + let mut want = [0u64; 3]; + for b in &blk { + if b.flags & F_INTRA != 0 { + want[0] += 1; + } else if b.flags & F_SKIP != 0 { + want[1] += 1; + } else if b.flags != 0 { + want[2] += 1; + } + } + assert_eq!(block_counts(&blk), want); + assert_eq!(block_counts(&[]), [0; 3]); + } +}