PR #25668 opened by romainhedouin
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25668
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25668.patch

The scratch buffer for the reordered and predicted pixels was allocated for 
every block and only freed when the encoder is closed, although it is only used 
inside the iteration of one block. Use one buffer per codec context instead 
(each frame thread has its own context). Only -compression rle, zip1 and zip16 
allocate it.

The output is byte-identical: the framemd5 of 2088 test cells (8 sizes from 1x1 
to 1921x1081, 3 pixel formats, 4 compression modes, float/half, 1/2/8 threads, 
smooth/constant/real/random content) is the same before and after with Apple 
clang and GCC 16, and fate-lavf-*.exr passes.

| float, gbrpf32le | RAM before | RAM after | CPU before | CPU after |
|---|---|---|---|---|
| 1080p zip16, 1 thread | 233 MiB | 208 MiB | 3.39 s | 3.43 s |
| 1080p zip1, 1 thread | 376 MiB | 347 MiB | 3.12 s | 2.97 s |
| 1080p rle, 1 thread | 435 MiB | 406 MiB | 2.59 s | 2.56 s |
| 1080p zip16, 8 threads | 1033 MiB | 833 MiB | 9.28 s | 9.26 s |
| 4K zip16, 8 threads | 4082 MiB | 3274 MiB | 30.1 s | 29.9 s |
| 1080p none, 1 thread (control) | 340 MiB | 340 MiB | 2.66 s | 2.66 s |

Noticed during a review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From 64efd92118e4b65bc5ce370e7ce97bcf72e70da6 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Sun, 1 Nov 2026 22:10:26 +0100
Subject: [PATCH] avcodec/exrenc: use one scratch buffer for all blocks

The scratch buffer for the reordered and predicted pixels was allocated
for every block and kept until the encoder is closed, although it is
only used inside the iteration of one block. Use one buffer per context
instead. The output is unchanged.

Noticed during a review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavcodec/exrenc.c | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/libavcodec/exrenc.c b/libavcodec/exrenc.c
index 68eb683aa5a..5f27030b2ae 100644
--- a/libavcodec/exrenc.c
+++ b/libavcodec/exrenc.c
@@ -66,9 +66,6 @@ typedef struct EXRScanlineData {
     uint8_t *uncompressed_data;
     unsigned int uncompressed_size;
 
-    uint8_t *tmp;
-    unsigned int tmp_size;
-
     int64_t actual_size;
 } EXRScanlineData;
 
@@ -87,6 +84,9 @@ typedef struct EXRContext {
 
     EXRScanlineData *scanline;
 
+    uint8_t *tmp;
+    unsigned int tmp_size;
+
     Float2HalfTables f2h_tables;
 } EXRContext;
 
@@ -145,12 +145,12 @@ static av_cold int encode_close(AVCodecContext *avctx)
     for (int y = 0; y < s->nb_scanlines && s->scanline; y++) {
         EXRScanlineData *scanline = &s->scanline[y];
 
-        av_freep(&scanline->tmp);
         av_freep(&scanline->compressed_data);
         av_freep(&scanline->uncompressed_data);
     }
 
     av_freep(&s->scanline);
+    av_freep(&s->tmp);
 
     return 0;
 }
@@ -231,8 +231,8 @@ static int encode_scanline_rle(EXRContext *s, const AVFrame 
*frame)
         if (!scanline->uncompressed_data)
             return AVERROR(ENOMEM);
 
-        av_fast_padded_malloc(&scanline->tmp, &scanline->tmp_size, tmp_size);
-        if (!scanline->tmp)
+        av_fast_padded_malloc(&s->tmp, &s->tmp_size, tmp_size);
+        if (!s->tmp)
             return AVERROR(ENOMEM);
 
         av_fast_padded_malloc(&scanline->compressed_data, 
&scanline->compressed_size, max_compressed_size);
@@ -260,11 +260,11 @@ static int encode_scanline_rle(EXRContext *s, const 
AVFrame *frame)
             break;
         }
 
-        reorder_pixels(scanline->tmp, scanline->uncompressed_data, tmp_size);
-        predictor(scanline->tmp, tmp_size);
+        reorder_pixels(s->tmp, scanline->uncompressed_data, tmp_size);
+        predictor(s->tmp, tmp_size);
         scanline->actual_size = rle_compress(scanline->compressed_data,
                                              max_compressed_size,
-                                             scanline->tmp, tmp_size);
+                                             s->tmp, tmp_size);
 
         if (scanline->actual_size <= 0 || scanline->actual_size >= tmp_size) {
             FFSWAP(uint8_t *, scanline->uncompressed_data, 
scanline->compressed_data);
@@ -291,8 +291,8 @@ static int encode_scanline_zip(EXRContext *s, const AVFrame 
*frame)
         if (!scanline->uncompressed_data)
             return AVERROR(ENOMEM);
 
-        av_fast_padded_malloc(&scanline->tmp, &scanline->tmp_size, tmp_size);
-        if (!scanline->tmp)
+        av_fast_padded_malloc(&s->tmp, &s->tmp_size, tmp_size);
+        if (!s->tmp)
             return AVERROR(ENOMEM);
 
         av_fast_padded_malloc(&scanline->compressed_data, 
&scanline->compressed_size, max_compressed_size);
@@ -329,12 +329,12 @@ static int encode_scanline_zip(EXRContext *s, const 
AVFrame *frame)
             break;
         }
 
-        reorder_pixels(scanline->tmp, scanline->uncompressed_data, tmp_size);
-        predictor(scanline->tmp, tmp_size);
+        reorder_pixels(s->tmp, scanline->uncompressed_data, tmp_size);
+        predictor(s->tmp, tmp_size);
         source_size = tmp_size;
         actual_size = max_compressed_size;
         compress(scanline->compressed_data, &actual_size,
-                 scanline->tmp, source_size);
+                 s->tmp, source_size);
 
         scanline->actual_size = actual_size;
         if (scanline->actual_size >= tmp_size) {
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to