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]

Reply via email to