For 4K/8K video processing, it's possible to have over 1,000 tasks pending on the executor. In such cases, O(n) and O(log(n)) insertion times are too costly. Reducing this to O(1) will significantly decrease the time spent in critical sections clip | before | after | delta ------------------------------------------------------------|--------|--------|------- VVC_HDR_UHDTV2_OpenGOP_7680x4320_50fps_HLG10.bit | 24 | 27 | 12.5% VVC_HDR_UHDTV2_OpenGOP_7680x4320_50fps_HLG10_HighBitrate.bit| 12 | 17 | 41.7% tears_of_steel_4k_8M_8bit_2000.vvc | 34 | 102 | 200.0% VVC_UHDTV1_OpenGOP_3840x2160_60fps_HLG10.bit | 126 | 128 | 1.6% RitualDance_1920x1080_60_10_420_37_RA.266 | 350 | 378 | 8.0% NovosobornayaSquare_1920x1080.bin | 341 | 369 | 8.2% Tango2_3840x2160_60_10_420_27_LD.266 | 69 | 70 | 1.4% RitualDance_1920x1080_60_10_420_32_LD.266 | 243 | 259 | 6.6% Chimera_8bit_1080P_1000_frames.vvc | 420 | 392 | -6.7% BQTerrace_1920x1080_60_10_420_22_RA.vvc | 148 | 144 | -2.7%
Showing the changes to libavcodec (3 of 3 files). Show all
Unified · Split
| @@ -48,6 +48,11 @@ typedef struct ThreadInfo { · 47 unmodified lines | |||
| 48 | ExecutorThread thread; | 48 | ExecutorThread thread; |
| 49 | } ThreadInfo; | 49 | } ThreadInfo; |
| 50 | 50 | ||
| 51 | typedef struct Queue { | ||
| 52 | FFTask *head; | ||
| 53 | FFTask *tail; | ||
| 54 | } Queue; | ||
| 55 | |||
| 51 | struct FFExecutor { | 56 | struct FFExecutor { |
| 52 | FFTaskCallbacks cb; | 57 | FFTaskCallbacks cb; |
| 53 | int thread_count; | 58 | int thread_count; |
| @@ -60,29 +65,39 @@ struct FFExecutor { · 6 unmodified lines | |||
| 60 | AVCond cond; | 65 | AVCond cond; |
| 61 | int die; | 66 | int die; |
| 62 | 67 | ||
| 63 | FFTask *tasks; | 68 | Queue *q; |
| 64 | }; | 69 | }; |
| 65 | 70 | ||
| 66 | static FFTask* remove_task(FFTask **prev, FFTask *t) | 71 | static FFTask* remove_task(Queue *q) |
| 67 | { | 72 | { |
| 68 | *prev = t->next; | 73 | FFTask *t = q->head; |
| 69 | t->next = NULL; | 74 | if (t) { |
| 75 | q->head = t->next; | ||
| 76 | t->next = NULL; | ||
| 77 | if (!q->head) | ||
| 78 | q->tail = NULL; | ||
| 79 | } | ||
| 70 | return t; | 80 | return t; |
| 71 | } | 81 | } |
| 72 | 82 | ||
| 73 | static void add_task(FFTask **prev, FFTask *t) | 83 | static void add_task(Queue *q, FFTask *t) |
| 74 | { | 84 | { |
| 75 | t->next = *prev; | 85 | t->next = NULL; |
| 76 | *prev = t; | 86 | if (!q->head) |
| 87 | q->tail = q->head = t; | ||
| 88 | else | ||
| 89 | q->tail = q->tail->next = t; | ||
| 77 | } | 90 | } |
| 78 | 91 | ||
| 79 | static int run_one_task(FFExecutor *e, void *lc) | 92 | static int run_one_task(FFExecutor *e, void *lc) |
| 80 | { | 93 | { |
| 81 | FFTaskCallbacks *cb = &e->cb; | 94 | FFTaskCallbacks *cb = &e->cb; |
| 82 | FFTask **prev = &e->tasks; | 95 | FFTask *t = NULL; |
| 96 | |||
| 97 | for (int i = 0; i < e->cb.priorities && !t; i++) | ||
| 98 | t = remove_task(e->q + i); | ||
| 83 | 99 | ||
| 84 | if (*prev) { | 100 | if (t) { |
| 85 | FFTask *t = remove_task(prev, *prev); | ||
| 86 | if (e->thread_count > 0) | 101 | if (e->thread_count > 0) |
| 87 | ff_mutex_unlock(&e->lock); | 102 | ff_mutex_unlock(&e->lock); |
| 88 | cb->run(t, lc, cb->user_data); | 103 | cb->run(t, lc, cb->user_data); |
| @@ -132,6 +147,7 @@ static void executor_free(FFExecutor *e, const int has_lock, const int has_cond) · 43 unmodified lines | |||
| 132 | ff_mutex_destroy(&e->lock); | 147 | ff_mutex_destroy(&e->lock); |
| 133 | 148 | ||
| 134 | av_free(e->threads); | 149 | av_free(e->threads); |
| 150 | av_free(e->q); | ||
| 135 | av_free(e->local_contexts); | 151 | av_free(e->local_contexts); |
| 136 | 152 | ||
| 137 | av_free(e); | 153 | av_free(e); |
| @@ -141,7 +157,7 @@ FFExecutor* ff_executor_alloc(const FFTaskCallbacks *cb, int thread_count) · 3 unmodified lines | |||
| 141 | { | 157 | { |
| 142 | FFExecutor *e; | 158 | FFExecutor *e; |
| 143 | int has_lock = 0, has_cond = 0; | 159 | int has_lock = 0, has_cond = 0; |
| 144 | if (!cb || !cb->user_data || !cb->run || !cb->priority_higher) | 160 | if (!cb || !cb->user_data || !cb->run || !cb->priorities) |
| 145 | return NULL; | 161 | return NULL; |
| 146 | 162 | ||
| 147 | e = av_mallocz(sizeof(*e)); | 163 | e = av_mallocz(sizeof(*e)); |
| @@ -153,6 +169,10 @@ FFExecutor* ff_executor_alloc(const FFTaskCallbacks *cb, int thread_count) · 5 unmodified lines | |||
| 153 | if (!e->local_contexts) | 169 | if (!e->local_contexts) |
| 154 | goto free_executor; | 170 | goto free_executor; |
| 155 | 171 | ||
| 172 | e->q = av_calloc(e->cb.priorities, sizeof(Queue)); | ||
| 173 | if (!e->q) | ||
| 174 | goto free_executor; | ||
| 175 | |||
| 156 | e->threads = av_calloc(FFMAX(thread_count, 1), sizeof(*e->threads)); | 176 | e->threads = av_calloc(FFMAX(thread_count, 1), sizeof(*e->threads)); |
| 157 | if (!e->threads) | 177 | if (!e->threads) |
| 158 | goto free_executor; | 178 | goto free_executor; |
| @@ -192,16 +212,10 @@ void ff_executor_free(FFExecutor **executor) · 33 unmodified lines | |||
| 192 | 212 | ||
| 193 | void ff_executor_execute(FFExecutor *e, FFTask *t) | 213 | void ff_executor_execute(FFExecutor *e, FFTask *t) |
| 194 | { | 214 | { |
| 195 | FFTaskCallbacks *cb = &e->cb; | ||
| 196 | FFTask **prev; | ||
| 197 | |||
| 198 | if (e->thread_count) | 215 | if (e->thread_count) |
| 199 | ff_mutex_lock(&e->lock); | 216 | ff_mutex_lock(&e->lock); |
| 200 | if (t) { | 217 | if (t) |
| 201 | for (prev = &e->tasks; *prev && cb->priority_higher(*prev, t); prev = &(*prev)->next) | 218 | add_task(e->q + t->priority % e->cb.priorities, t); |
| 202 | /* nothing */; | ||
| 203 | add_task(prev, t); | ||
| 204 | } | ||
| 205 | if (e->thread_count) { | 219 | if (e->thread_count) { |
| 206 | ff_cond_signal(&e->cond); | 220 | ff_cond_signal(&e->cond); |
| 207 | ff_mutex_unlock(&e->lock); | 221 | ff_mutex_unlock(&e->lock); |
| @@ -32,6 +32,7 @@ typedef struct FFTask FFTask; · 31 unmodified lines | |||
| 32 | 32 | ||
| 33 | struct FFTask { | 33 | struct FFTask { |
| 34 | FFTask *next; | 34 | FFTask *next; |
| 35 | int priority; // task priority should >= 0 and < AVTaskCallbacks.priorities | ||
| 35 | }; | 36 | }; |
| 36 | 37 | ||
| 37 | typedef struct FFTaskCallbacks { | 38 | typedef struct FFTaskCallbacks { |
| @@ -39,8 +40,8 @@ typedef struct FFTaskCallbacks { · 1 unmodified line | |||
| 39 | 40 | ||
| 40 | int local_context_size; | 41 | int local_context_size; |
| 41 | 42 | ||
| 42 | // return 1 if a's priority > b's priority | 43 | // how many priorities do we have? |
| 43 | int (*priority_higher)(const FFTask *a, const FFTask *b); | 44 | int priorities; |
| 44 | 45 | ||
| 45 | // run the task | 46 | // run the task |
| 46 | int (*run)(FFTask *t, void *local_context, void *user_data); | 47 | int (*run)(FFTask *t, void *local_context, void *user_data); |
| @@ -103,13 +103,28 @@ typedef struct VVCFrameThread { · 102 unmodified lines | |||
| 103 | AVCond cond; | 103 | AVCond cond; |
| 104 | } VVCFrameThread; | 104 | } VVCFrameThread; |
| 105 | 105 | ||
| 106 | #define PRIORITY_LOWEST 2 | ||
| 106 | static void add_task(VVCContext *s, VVCTask *t) | 107 | static void add_task(VVCContext *s, VVCTask *t) |
| 107 | { | 108 | { |
| 108 | VVCFrameThread *ft = t->fc->ft; | 109 | VVCFrameThread *ft = t->fc->ft; |
| 110 | FFTask *task = &t->u.task; | ||
| 111 | const int priorities[] = { | ||
| 112 | 0, // VVC_TASK_STAGE_INIT, | ||
| 113 | 0, // VVC_TASK_STAGE_PARSE, | ||
| 114 | // For an 8K clip, a CTU line completed in the reference frame may trigger 64 and more inter tasks. | ||
| 115 | // We assign these tasks the lowest priority to avoid being overwhelmed with inter tasks. | ||
| 116 | PRIORITY_LOWEST, // VVC_TASK_STAGE_INTER | ||
| 117 | 1, // VVC_TASK_STAGE_RECON, | ||
| 118 | 1, // VVC_TASK_STAGE_LMCS, | ||
| 119 | 1, // VVC_TASK_STAGE_DEBLOCK_V, | ||
| 120 | 1, // VVC_TASK_STAGE_DEBLOCK_H, | ||
| 121 | 1, // VVC_TASK_STAGE_SAO, | ||
| 122 | 1, // VVC_TASK_STAGE_ALF, | ||
| 123 | }; | ||
| 109 | 124 | ||
| 110 | atomic_fetch_add(&ft->nb_scheduled_tasks, 1); | 125 | atomic_fetch_add(&ft->nb_scheduled_tasks, 1); |
| 111 | 126 | task->priority = priorities[t->stage]; | |
| 112 | ff_executor_execute(s->executor, &t->u.task); | 127 | ff_executor_execute(s->executor, task); |
| 113 | } | 128 | } |
| 114 | 129 | ||
| 115 | static void task_init(VVCTask *t, VVCTaskStage stage, VVCFrameContext *fc, const int rx, const int ry) | 130 | static void task_init(VVCTask *t, VVCTaskStage stage, VVCFrameContext *fc, const int rx, const int ry) |
| @@ -372,31 +387,6 @@ static int task_is_stage_ready(VVCTask *t, int add) · 256 unmodified lines | |||
| 372 | return task_has_target_score(t, stage, score); | 387 | return task_has_target_score(t, stage, score); |
| 373 | } | 388 | } |
| 374 | 389 | ||
| 375 | #define CHECK(a, b) \ | ||
| 376 | do { \ | ||
| 377 | if ((a) != (b)) \ | ||
| 378 | return (a) < (b); \ | ||
| 379 | } while (0) | ||
| 380 | |||
| 381 | static int task_priority_higher(const FFTask *_a, const FFTask *_b) | ||
| 382 | { | ||
| 383 | const VVCTask *a = (const VVCTask*)_a; | ||
| 384 | const VVCTask *b = (const VVCTask*)_b; | ||
| 385 | |||
| 386 | |||
| 387 | if (a->stage <= VVC_TASK_STAGE_PARSE || b->stage <= VVC_TASK_STAGE_PARSE) { | ||
| 388 | CHECK(a->stage, b->stage); | ||
| 389 | CHECK(a->fc->decode_order, b->fc->decode_order); //decode order | ||
| 390 | CHECK(a->ry, b->ry); | ||
| 391 | return a->rx < b->rx; | ||
| 392 | } | ||
| 393 | |||
| 394 | CHECK(a->fc->decode_order, b->fc->decode_order); //decode order | ||
| 395 | CHECK(a->rx + a->ry + a->stage, b->rx + b->ry + b->stage); //zigzag with type | ||
| 396 | CHECK(a->rx + a->ry, b->rx + b->ry); //zigzag | ||
| 397 | return a->ry < b->ry; | ||
| 398 | } | ||
| 399 | |||
| 400 | static void check_colocation(VVCContext *s, VVCTask *t) | 390 | static void check_colocation(VVCContext *s, VVCTask *t) |
| 401 | { | 391 | { |
| 402 | const VVCFrameContext *fc = t->fc; | 392 | const VVCFrameContext *fc = t->fc; |
| @@ -681,7 +671,7 @@ FFExecutor* ff_vvc_executor_alloc(VVCContext *s, const int thread_count) · 278 unmodified lines | |||
| 681 | FFTaskCallbacks callbacks = { | 671 | FFTaskCallbacks callbacks = { |
| 682 | s, | 672 | s, |
| 683 | sizeof(VVCLocalContext), | 673 | sizeof(VVCLocalContext), |
| 684 | task_priority_higher, | 674 | PRIORITY_LOWEST + 1, |
| 685 | task_run, | 675 | task_run, |
| 686 | }; | 676 | }; |
| 687 | return ff_executor_alloc(&callbacks, thread_count); | 677 | return ff_executor_alloc(&callbacks, thread_count); |