From 24f5cc451ab68e5a8b0fdb9b8aeee99664dd2013 Mon Sep 17 00:00:00 2001
From: Sebastian Pop <spop@amazon.com>
Date: Thu, 2 Jul 2020 16:57:58 +0000
Subject: [PATCH] improve hscale with multi-threading

hscale is bound by the number of multiply-adds available per core.
The patch distributes the computations to helper threads.

With 4 helper threads, the performance improves up to 135% on c6g.metal
with Graviton2 Arm Neoverse-N1.

$ ./ffmpeg_g -nostats -f lavfi -i testsrc2=4k:d=2 -vf bench=start,scale=1024x1024,bench=stop -f null -

scale=1024x1024  72% improvement
before: [bench @ 0xaaaad62c3d30] t:0.013293 avg:0.013315 max:0.013697 min:0.013293
after:  [bench @ 0xaaaad5fb1d30] t:0.007595 avg:0.007717 max:0.009845 min:0.007491

scale=1280x720  83% improvement
before: [bench @ 0xaaaadba88d30] t:0.015973 avg:0.016321 max:0.016917 min:0.015973
after:  [bench @ 0xaaaaeb876d30] t:0.008961 avg:0.008939 max:0.010105 min:0.008757

scale=852x480  98% improvement
before: [bench @ 0xaaaaeeed0d30] t:0.013731 avg:0.013727 max:0.013773 min:0.013279
after:  [bench @ 0xaaaace751d30] t:0.006967 avg:0.006917 max:0.008426 min:0.006707

scale=640x360  93% improvement
before: [bench @ 0xaaaacee25d30] t:0.012010 avg:0.012006 max:0.012053 min:0.011653
after:  [bench @ 0xaaab162acd30] t:0.006074 avg:0.006207 max:0.007619 min:0.006020

scale=284x160  135% improvement
before: [bench @ 0xaaaadbb9ed30] t:0.008384 avg:0.008367 max:0.008421 min:0.008193
after:  [bench @ 0xaaaae1cb9d30] t:0.003475 avg:0.003557 max:0.006897 min:0.003421

With 2 helper threads the performance improves by up to 95% on a c5.metal instance
with Intel(R) Xeon(R) Platinum 8275CL CPU @ 3.00GHz

scale=1024x1024  56% improvement
before: [bench @ 0x55790f577540] t:0.005805 avg:0.005787 max:0.006763 min:0.005598
after:  [bench @ 0x562389919540] t:0.003623 avg:0.003708 max:0.005088 min:0.003565

scale=1280x720  67% improvement
before: [bench @ 0x5638390e3540] t:0.007121 avg:0.007099 max:0.007968 min:0.006824
after: [bench @ 0x559cf6923540] t:0.004169 avg:0.004242 max:0.005458 min:0.004036

scale=852x480  64% improvement
before: [bench @ 0x56445643a540] t:0.005292 avg:0.005377 max:0.005866 min:0.005281
after: [bench @ 0x5637d9ff1540] t:0.003013 avg:0.003283 max:0.005485 min:0.002990

scale=640x360  73% improvement
before: [bench @ 0x5571817cd540] t:0.004787 avg:0.004822 max:0.005166 min:0.004684
after:  [bench @ 0x55e1bb94a540] t:0.002702 avg:0.002787 max:0.003233 min:0.002688

scale=284x160  95% improvement
before: [bench @ 0x5646397a3540] t:0.003622 avg:0.003633 max:0.003862 min:0.003560
after:  [bench @ 0x5644020b7540] t:0.001817 avg:0.001866 max:0.002168 min:0.001780
---
 libswscale/hscale.c           | 105 ++++++++++++++++++++++++++++++++--
 libswscale/swscale_internal.h |  19 ++++++
 libswscale/utils.c            |  24 ++++++++
 3 files changed, 144 insertions(+), 4 deletions(-)

diff --git a/libswscale/hscale.c b/libswscale/hscale.c
index eca0635338..b3a8fa089e 100644
--- a/libswscale/hscale.c
+++ b/libswscale/hscale.c
@@ -35,7 +35,7 @@ typedef struct ColorContext
     uint32_t *pal;
 } ColorContext;
 
-static int lum_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int sliceH)
+static void lum_h_scale_1(int start, int end, SwsContext *c, SwsFilterDescriptor *desc, int sliceY)
 {
     FilterContext *instance = desc->instance;
     int srcW = desc->src->width;
@@ -43,7 +43,7 @@ static int lum_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int
     int xInc = instance->xInc;
 
     int i;
-    for (i = 0; i < sliceH; ++i) {
+    for (i = start; i < end; ++i) {
         uint8_t ** src = desc->src->plane[0].line;
         uint8_t ** dst = desc->dst->plane[0].line;
         int src_pos = sliceY+i - desc->src->plane[0].sliceY;
@@ -79,7 +79,35 @@ static int lum_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int
             }
         }
     }
+}
 
+static int lum_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int sliceH)
+{
+    int i;
+    int start = 0, end, slice;
+    if (!c || HT_NUM == 0 || 0 != pthread_mutex_trylock(&c->ht_mutex)) {
+        lum_h_scale_1(0, sliceH, c, desc, sliceY);
+        return sliceH;
+    }
+
+    /* A single thread per SwsContext can lock the mutex to use helper threads.  */
+    end = slice = sliceH / (HT_NUM + 1);
+    c->ht_desc = desc;
+    c->ht_sliceY = sliceY;
+    for (i = 0; i < HT_NUM; i++) {
+        c->ht_start[i] = start;
+        c->ht_end[i] = end;
+        __atomic_store_n(&c->ht_work[i], HT_LUM_H_SCALE, __ATOMIC_RELEASE);
+        start += slice;
+        end += slice;
+    }
+
+    lum_h_scale_1(start, sliceH, c, desc, sliceY);
+
+    /* Wait for the helper threads to finish work.  */
+    for (i = 0; i < HT_NUM; i++)
+        do {} while (HT_NO_WORK != __atomic_load_n(&c->ht_work[i], __ATOMIC_ACQUIRE));
+    pthread_mutex_unlock (&c->ht_mutex);
     return sliceH;
 }
 
@@ -163,7 +191,7 @@ int ff_init_desc_hscale(SwsFilterDescriptor *desc, SwsSlice *src, SwsSlice *dst,
     return 0;
 }
 
-static int chr_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int sliceH)
+static void chr_h_scale_1(int start, int end, SwsContext *c, SwsFilterDescriptor *desc, int sliceY)
 {
     FilterContext *instance = desc->instance;
     int srcW = AV_CEIL_RSHIFT(desc->src->width, desc->src->h_chr_sub_sample);
@@ -182,7 +210,7 @@ static int chr_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int
     int dst_pos2 = sliceY - desc->dst->plane[2].sliceY;
 
     int i;
-    for (i = 0; i < sliceH; ++i) {
+    for (i = start; i < end; ++i) {
         if (c->hcscale_fast) {
             c->hcscale_fast(c, (uint16_t*)dst1[dst_pos1+i], (uint16_t*)dst2[dst_pos2+i], dstW, src1[src_pos1+i], src2[src_pos2+i], srcW, xInc);
         } else {
@@ -196,9 +224,78 @@ static int chr_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int
         desc->dst->plane[1].sliceH += 1;
         desc->dst->plane[2].sliceH += 1;
     }
+}
+
+static int chr_h_scale(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int sliceH)
+{
+    int i;
+    int start = 0, end, slice;
+    if (!c || HT_NUM == 0 || 0 != pthread_mutex_trylock(&c->ht_mutex)) {
+        chr_h_scale_1(0, sliceH, c, desc, sliceY);
+        return sliceH;
+    }
+
+    /* A single thread per SwsContext can lock the mutex to use helper threads.  */
+    end = slice = sliceH / (HT_NUM + 1);
+    c->ht_desc = desc;
+    c->ht_sliceY = sliceY;
+    for (i = 0; i < HT_NUM; i++) {
+        c->ht_start[i] = start;
+        c->ht_end[i] = end;
+        __atomic_store_n(&c->ht_work[i], HT_CHR_H_SCALE, __ATOMIC_RELEASE);
+        start += slice;
+        end += slice;
+    }
+
+    chr_h_scale_1(start, sliceH, c, desc, sliceY);
+
+    /* Wait for the helper threads to finish work.  */
+    for (i = 0; i < HT_NUM; i++)
+        do {} while (HT_NO_WORK != __atomic_load_n(&c->ht_work[i], __ATOMIC_ACQUIRE));
+    pthread_mutex_unlock (&c->ht_mutex);
     return sliceH;
 }
 
+void *helper_thread_fun(void *ctx) {
+  SwsContext *c = (SwsContext *)ctx;
+
+  /* Find index i of current thread. */
+  int i = 0;
+  pthread_t self;
+
+  if (HT_NUM == 0)
+      return 0;
+
+  self = pthread_self();
+  for (i = 0; i < HT_NUM; i++)
+      if (pthread_equal(self, c->helper_threads[i]))
+          break;
+
+  do {
+    switch (__atomic_load_n(&c->ht_work[i], __ATOMIC_ACQUIRE)) {
+    case HT_LUM_H_SCALE:
+      lum_h_scale_1(c->ht_start[i], c->ht_end[i], c, c->ht_desc, c->ht_sliceY);
+      __atomic_store_n(&c->ht_work[i], HT_NO_WORK, __ATOMIC_RELEASE);
+      break;
+
+    case HT_CHR_H_SCALE:
+      chr_h_scale_1(c->ht_start[i], c->ht_end[i], c, c->ht_desc, c->ht_sliceY);
+      __atomic_store_n(&c->ht_work[i], HT_NO_WORK, __ATOMIC_RELEASE);
+      break;
+
+    case HT_EXIT: {
+      int ret = 0;
+      pthread_exit(&ret);
+    }
+
+    case HT_NO_WORK:
+    default:
+      break;
+    }
+    sched_yield();
+  } while (1);
+}
+
 static int chr_convert(SwsContext *c, SwsFilterDescriptor *desc, int sliceY, int sliceH)
 {
     int srcW = AV_CEIL_RSHIFT(desc->src->width, desc->src->h_chr_sub_sample);
diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h
index 1a1b6f0dee..89574f412e 100644
--- a/libswscale/swscale_internal.h
+++ b/libswscale/swscale_internal.h
@@ -23,6 +23,7 @@
 
 #include "config.h"
 #include "version.h"
+#include "pthread.h"
 
 #include "libavutil/avassert.h"
 #include "libavutil/avutil.h"
@@ -625,9 +626,27 @@ typedef struct SwsContext {
     SwsDither dither;
 
     SwsAlphaBlend alphablend;
+
+/* Helper threads are disabled by default.  */
+#ifndef HT_NUM
+#define HT_NUM 0
+#endif
+    pthread_t helper_threads[HT_NUM];
+    pthread_mutex_t ht_mutex;
+    int ht_work[HT_NUM];
+#define HT_NO_WORK 0
+#define HT_LUM_H_SCALE 1
+#define HT_CHR_H_SCALE 2
+#define HT_EXIT 3
+    struct SwsFilterDescriptor *ht_desc;
+    int ht_sliceY;
+    int ht_start[HT_NUM];
+    int ht_end[HT_NUM];
 } SwsContext;
 //FIXME check init (where 0)
 
+void *helper_thread_fun(void *ctx);
+
 SwsFunc ff_yuv2rgb_get_func_ptr(SwsContext *c);
 int ff_yuv2rgb_c_init_tables(SwsContext *c, const int inv_table[4],
                              int fullRange, int brightness,
diff --git a/libswscale/utils.c b/libswscale/utils.c
index dcd1dbaa76..d7d784df97 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -1846,6 +1846,18 @@ av_cold int sws_init_context(SwsContext *c, SwsFilter *srcFilter,
         }
     }
 
+    if (HT_NUM > 0) {
+        pthread_mutexattr_t attr;
+        pthread_mutexattr_init(&attr);
+        if (pthread_mutex_init(&c->ht_mutex, &attr))
+            goto fail;
+        for (i = 0; i < HT_NUM; i++) {
+            c->ht_work[i] = HT_NO_WORK;
+            if (pthread_create (&c->helper_threads[i], NULL, helper_thread_fun, (void *)c))
+                goto fail;
+        }
+    }
+
     c->swscale = ff_getSwsFunc(c);
     return ff_init_filters(c);
 nomem:
@@ -2385,6 +2397,18 @@ void sws_freeContext(SwsContext *c)
 
     ff_free_filters(c);
 
+    for (i = 0; i < HT_NUM; i++)
+        c->ht_work[i] = HT_EXIT;
+
+    if (HT_NUM) {
+        void *ret;
+        /* Wait until all helper threads have finished. */
+        for (i = 0; i < HT_NUM; i++)
+            pthread_join(c->helper_threads[i], &ret);
+
+        pthread_mutex_destroy(&c->ht_mutex);
+    }
+
     av_free(c);
 }
 
-- 
2.25.1

