File 2587-Steal-tasks-in-blocks.patch of Package erlang

From ef8430c61ebb25c9dce9327f4641352361d9572b Mon Sep 17 00:00:00 2001
From: Robin Morisset <rmorisset@meta.com>
Date: Wed, 23 Oct 2024 03:38:49 -0700
Subject: [PATCH 07/15] Steal tasks in blocks

Instead of using a stack to store the tasks being stolen, and turn them
back into a linked list at the end, we can store them as a linked-list
throughout. The main benefit is shrinking the critical section where we
re-enqueue them.
---
 erts/emulator/beam/erl_process.c | 69 +++++++++++++++++---------------
 1 file changed, 36 insertions(+), 33 deletions(-)

diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 6dee6c0747..f1681a1c1b 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -4465,26 +4465,21 @@ evacuate_run_queue(ErtsRunQueue *rq,
     }
 }
 
-typedef struct ErtsStolenProcess_ {
-    Process *proc;
-    int prio;
-} ErtsStolenProcess;
-
 static int
 try_steal_task_from_victim(ErtsRunQueue *rq, ErtsRunQueue *vrq, Uint32 flags, Process **result_proc)
 {
     Uint32 procs_qmask = flags & ERTS_RUNQ_FLGS_PROCS_QMASK;
     int max_prio_bit;
     ErtsRunPrioQueue *rpq;
-#define PSTACK_TYPE ErtsStolenProcess
-    PSTACK_DECLARE(stolen_processes, 16);
+    Process *first_stolen_proc = NULL;
+    Process *last_stolen_proc = NULL;
+    unsigned first_stolen_proc_prio;
 
     ERTS_LC_ASSERT(!erts_lc_runq_is_locked(rq));
 
     erts_runq_lock(vrq);
 
     if (ERTS_RUNQ_FLGS_GET_NOB(rq) & ERTS_RUNQ_FLG_HALTING) {
-        PSTACK_DESTROY(stolen_processes);
 	goto no_procs;
     }
 
@@ -4501,6 +4496,7 @@ try_steal_task_from_victim(ErtsRunQueue *rq, ErtsRunQueue *vrq, Uint32 flags, Pr
         ErtsRunQueueInfo *rqi;
 
 	max_prio_bit = procs_qmask & -procs_qmask;
+	procs_qmask &= ~max_prio_bit;
 	switch (max_prio_bit) {
 	case MAX_BIT:
             prio_q = PRIORITY_MAX;
@@ -4524,10 +4520,12 @@ try_steal_task_from_victim(ErtsRunQueue *rq, ErtsRunQueue *vrq, Uint32 flags, Pr
 	    goto no_procs;
 	default:
 	    ASSERT(!"Invalid queue mask");
-            PSTACK_DESTROY(stolen_processes);
 	    goto no_procs;
 	}
 
+        if (!max_processes_to_steal) {
+            continue;
+        }
         rpq = &vrq->procs.prio[prio_q];
         // Steal at least one task, even if there is a single one
         max_processes_to_steal++;
@@ -4544,19 +4542,22 @@ try_steal_task_from_victim(ErtsRunQueue *rq, ErtsRunQueue *vrq, Uint32 flags, Pr
             if (erts_try_change_runq_proc(proc, rq)) {
                 erts_aint32_t state = erts_atomic32_read_acqb(&proc->state);
                 int prio = (int) ERTS_PSFLGS_GET_PRQ_PRIO(state);
-                ErtsStolenProcess *sp = PSTACK_PUSH(stolen_processes);
-                sp->proc = proc;
-                sp->prio = prio;
-                n_procs_stolen[prio]++;
                 unqueue_process_no_update_lengths(rpq, prev_proc, proc);
+                if (!first_stolen_proc) {
+                    first_stolen_proc = proc;
+                    first_stolen_proc_prio = prio;
+                } else {
+                    last_stolen_proc->next = proc;
+                }
+                last_stolen_proc = proc;
+                n_procs_stolen[prio]++;
                 --max_processes_to_steal;
             } else {
                 prev_proc = proc;
             }
 	    proc = proc->next;
 	}
-        if (!PSTACK_IS_EMPTY(stolen_processes)) {
-            ErtsStolenProcess *sp = (ErtsStolenProcess *) stolen_processes.pstart;
+        if (first_stolen_proc) {
             for (int i = 0; i < ERTS_NO_PROC_PRIO_LEVELS; ++i) {
                 if (n_procs_stolen[i] > 0) {
                     ErtsRunQueueInfo *rqi = &vrq->procs.prio_info[i];
@@ -4564,30 +4565,32 @@ try_steal_task_from_victim(ErtsRunQueue *rq, ErtsRunQueue *vrq, Uint32 flags, Pr
                 }
             }
             erts_runq_unlock(vrq);
-            *result_proc = sp->proc;
-            ASSERT(n_procs_stolen[sp->prio] > 0);
-            n_procs_stolen[sp->prio]--; // We're not going to requeue this one, as we're returning it
-            ++sp;
+            *result_proc = first_stolen_proc;
+            ASSERT(n_procs_stolen[first_stolen_proc_prio] > 0);
+            n_procs_stolen[first_stolen_proc_prio]--; // We're not going to requeue this one, as we're returning it
+            ASSERT(last_stolen_proc);
+            last_stolen_proc->next = NULL;
+            first_stolen_proc = first_stolen_proc->next;
 
             erts_runq_lock(rq);
-            for (int i = 0; i < ERTS_NO_PROC_PRIO_LEVELS; ++i) {
-                if (n_procs_stolen[i] > 0) {
-                    ErtsRunQueueInfo *rqi = &rq->procs.prio_info[i];
-                    erts_add_runq_len(rq, rqi, i, n_procs_stolen[i]);
+            if (first_stolen_proc) {
+                for (int i = 0; i < ERTS_NO_PROC_PRIO_LEVELS; ++i) {
+                    if (n_procs_stolen[i] > 0) {
+                        ErtsRunQueueInfo *rqi = &rq->procs.prio_info[i];
+                        erts_add_runq_len(rq, rqi, i, n_procs_stolen[i]);
+                    }
                 }
+                rpq = &rq->procs.prio[prio_q];
+                // Someone may have pushed work to us while we were not holding our lock
+                if (rpq->last) {
+                    rpq->last->next = first_stolen_proc;
+                } else {
+                    rpq->first = first_stolen_proc;
+                }
+                rpq->last = last_stolen_proc;
             }
-            // We're not using a loop of PSTACK_POP to keep the right (LIFO) order of elements
-            // "<=" rather than "<" because of the insanity that is PSTACK (offs = 0 means that there is one element)
-            for (;(byte *) sp <= stolen_processes.pstart + stolen_processes.offs; ++sp) {
-                unsigned prio_q = sp->prio == PRIORITY_LOW ? PRIORITY_NORMAL : sp->prio;
-                enqueue_process_internal(&rq->procs.prio[prio_q], sp->proc);
-            }
-            PSTACK_DESTROY(stolen_processes);
             return !0;
         }
-        PSTACK_DESTROY(stolen_processes);
-
-	procs_qmask &= ~max_prio_bit;
     }
 
 no_procs:
-- 
2.43.0

openSUSE Build Service is sponsored by