PR #25768 opened by David C. Manuelda (StormBytePP) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25768 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25768.patch
### Motivation The BM3D performance issue was first reproduced by a BM3D fixture test in the StormByte-Multimedia project. It is especially pronounced during PGO profile collection, where atomic profile-counter updates add overhead to the filter's hot search loop. This patch reduces redundant work in that loop while preserving the matching threshold and ordering. ### Release benchmark Built FFmpeg with and without this patch from the same source revision, using GCC 16.2.1, Release mode, and LTO. Both builds completed the same 1080p BM3D workload successfully. | Build | Mean time | Standard deviation | |---|---:|---:| | Without patch | 17.205 s | 0.035 s | | With patch | 15.034 s | 0.013 s | The patched build was **1.14× faster**, with **12.6% lower mean elapsed time**. ### PGO-instrumented benchmark To measure the profile-collection overhead directly, both builds were compiled with GCC's `-fprofile-generate` instrumentation and atomic profile updates. Each binary used a separate profile directory. These are instrumented profile-generation builds, not binaries optimized with a collected profile. | Instrumented build | Mean time | Standard deviation | |---|---:|---:| | Without patch | 79.568 s | 0.740 s | | With patch | 27.043 s | 1.631 s | With instrumentation enabled, the patched build was **2.94× faster**, with **66.0% lower mean elapsed time** for this workload. Both benchmarks used `hyperfine` and the same BM3D filter settings for each respective comparison. The Release benchmark used one warmup and five measured runs. The PGO benchmark used one warmup and three measured runs; each run processed one 1920×1080 frame generated by `testsrc2`. The initial cold-run warning was addressed by using an explicit warmup; the figures above are the measured runs after warmup. The PGO results describe the cost of profile collection and should not be read as a comparison of final profile-optimized binaries. ```sh hyperfine --warmup 1 --runs 3 \ --command-name 'GCC PGO-instrumented FFmpeg without patch' \ './ffmpeg-without-patch -nostdin -hide_banner -v error -filter_threads 4 -f lavfi -i "testsrc2=size=1920x1080:rate=24:duration=1" -vf "bm3d=sigma=3:block=8:bstep=4:group=16:range=9:estim=basic" -frames:v 1 -an -f null -' \ --command-name 'GCC PGO-instrumented FFmpeg with patch' \ './ffmpeg-with-patch -nostdin -hide_banner -v error -filter_threads 4 -f lavfi -i "testsrc2=size=1920x1080:rate=24:duration=1" -vf "bm3d=sigma=3:block=8:bstep=4:group=16:range=9:estim=basic" -frames:v 1 -an -f null -' ``` From 2456bfbe0825fcf9617b484ce49756a7c781eb95 Mon Sep 17 00:00:00 2001 From: "David C. Manuelda" <[email protected]> Date: Wed, 4 Nov 2026 16:57:08 +0100 Subject: [PATCH] avfilter/bm3d: bound exact block-distance work Accumulate SSD in exact integer arithmetic with four samples per loop. Use 32-bit sums for 8-bit pixels and 64-bit sums for higher depths. Reject candidates as soon as their partial SSD exceeds the existing matching threshold, without changing accepted distances or ordering. This reduces floating-point work and atomic PGO counter updates in the innermost search loop while preserving the denoising parameters. Found-by: https://github.com/StormByte-Suite/StormByte-Multimedia Signed-off-by: David C. Manuelda <[email protected]> --- libavfilter/vf_bm3d.c | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/libavfilter/vf_bm3d.c b/libavfilter/vf_bm3d.c index 58910e2414c..81ff568d741 100644 --- a/libavfilter/vf_bm3d.c +++ b/libavfilter/vf_bm3d.c @@ -119,7 +119,7 @@ typedef struct BM3DContext { int y, int x, int block_size, float *dst); double (*do_block_ssd)(struct BM3DContext *s, PosCode *pos, const uint8_t *src, int src_stride, - int r_y, int r_x); + int r_y, int r_x, double limit); void (*do_output)(struct BM3DContext *s, uint8_t *dst, int dst_linesize, int plane, int nb_jobs); void (*block_filtering)(struct BM3DContext *s, @@ -226,20 +226,30 @@ static int cmp_scores(const void *a, const void *b) return FFDIFFSIGN(pair1->score, pair2->score); } -static double do_block_ssd(BM3DContext *s, PosCode *pos, const uint8_t *src, int src_stride, int r_y, int r_x) +static double do_block_ssd(BM3DContext *s, PosCode *pos, const uint8_t *src, int src_stride, int r_y, int r_x, double limit) { const uint8_t *srcp = src + pos->y * src_stride + pos->x; const uint8_t *refp = src + r_y * src_stride + r_x; const int block_size = s->block_size; - double dist = 0.; + uint32_t dist = 0; int x, y; for (y = 0; y < block_size; y++) { - for (x = 0; x < block_size; x++) { - double temp = refp[x] - srcp[x]; + for (x = 0; x + 3 < block_size; x += 4) { + const int d0 = refp[x] - srcp[x]; + const int d1 = refp[x + 1] - srcp[x + 1]; + const int d2 = refp[x + 2] - srcp[x + 2]; + const int d3 = refp[x + 3] - srcp[x + 3]; + dist += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3; + } + for (; x < block_size; x++) { + const int temp = refp[x] - srcp[x]; dist += temp * temp; } + if (dist > limit) + return dist; + srcp += src_stride; refp += src_stride; } @@ -247,20 +257,30 @@ static double do_block_ssd(BM3DContext *s, PosCode *pos, const uint8_t *src, int return dist; } -static double do_block_ssd16(BM3DContext *s, PosCode *pos, const uint8_t *src, int src_stride, int r_y, int r_x) +static double do_block_ssd16(BM3DContext *s, PosCode *pos, const uint8_t *src, int src_stride, int r_y, int r_x, double limit) { const uint16_t *srcp = (uint16_t *)src + pos->y * src_stride / 2 + pos->x; const uint16_t *refp = (uint16_t *)src + r_y * src_stride / 2 + r_x; const int block_size = s->block_size; - double dist = 0.; + uint64_t dist = 0; int x, y; for (y = 0; y < block_size; y++) { - for (x = 0; x < block_size; x++) { - double temp = refp[x] - srcp[x]; + for (x = 0; x + 3 < block_size; x += 4) { + const int64_t d0 = refp[x] - srcp[x]; + const int64_t d1 = refp[x + 1] - srcp[x + 1]; + const int64_t d2 = refp[x + 2] - srcp[x + 2]; + const int64_t d3 = refp[x + 3] - srcp[x + 3]; + dist += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3; + } + for (; x < block_size; x++) { + const int64_t temp = refp[x] - srcp[x]; dist += temp * temp; } + if (dist > limit) + return dist; + srcp += src_stride / 2; refp += src_stride / 2; } @@ -282,7 +302,7 @@ static void do_block_matching_multi(BM3DContext *s, const uint8_t *src, int src_ PosCode pos = search_pos[i]; double dist; - dist = s->do_block_ssd(s, &pos, src, src_stride, r_y, r_x); + dist = s->do_block_ssd(s, &pos, src, src_stride, r_y, r_x, th_sse); // Only match similar blocks but not identical blocks if (dist <= th_sse && dist != 0) { -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
