* [PATCH 1/2] drivers: android: binder: implement CTABS heuristic
2026-07-14 18:35 [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection trieu2.huynh
@ 2026-07-14 18:35 ` trieu2.huynh
2026-07-15 5:55 ` Greg KH
2026-07-14 18:35 ` [PATCH 2/2] drivers: android: binder: add preferred CPU wake-up nudge for waiting threads trieu2.huynh
2026-07-15 9:17 ` [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection Alice Ryhl
2 siblings, 1 reply; 5+ messages in thread
From: trieu2.huynh @ 2026-07-14 18:35 UTC (permalink / raw)
To: gregkh
Cc: arve, tkjos, brauner, cmllamas, aliceryhl, linux-kernel,
trieu2.huynh
Introduce Cluster/Topology-Aware Binder Selection (CTABS) to optimize
IPC transaction dispatching.
On asymmetric multi-cluster ARM64 architectures (e.g., big.LITTLE), the
default binder thread selection algorithm is topology-blind. This leads
to frequent cross-cluster thread wakeups, resulting in severe CPU cache
misses, interconnect bottlenecks, and unnecessary cache ping-pongs.
To mitigate this latency penalty, CTABS evaluates idle waiting threads
based on their last-executed CPU ID and prioritizing them as follows:
(*) Immediate Selection: Favors threads whose last-executed CPU matches
the caller's CPU exactly, exiting the selection loop early to ensure
optimal L1/L2 cache locality.
(*) Cluster Match: Selects threads that ran on the same CPU cluster as
the caller, maximizing local cache reuse.
(*) Cross-Cluster: Falls back to any available thread across different
clusters if no local affinity is found.
This topology prioritization maximizes CPU cache reuse, minimizes inter-
cluster snooping overhead, and significantly reduces overall transaction
turnaround latency on modern multi-cluster SoCs.
Signed-off-by: trieu2.huynh <trieu2.huynh@lge.corp-partner.google.com>
---
drivers/android/binder.c | 76 +++++++++++++++++++++++++++++--
drivers/android/binder_internal.h | 2 +
2 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/drivers/android/binder.c b/drivers/android/binder.c
index 8f2ef1bd539f..2a7be45368c1 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -42,6 +42,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cpumask.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/freezer.h>
@@ -52,6 +53,7 @@
#include <linux/mutex.h>
#include <linux/nsproxy.h>
#include <linux/poll.h>
+#include <linux/preempt.h>
#include <linux/debugfs.h>
#include <linux/rbtree.h>
#include <linux/sched/signal.h>
@@ -65,6 +67,7 @@
#include <linux/ratelimit.h>
#include <linux/syscalls.h>
#include <linux/task_work.h>
+#include <linux/topology.h>
#include <linux/sizes.h>
#include <linux/ktime.h>
@@ -624,6 +627,56 @@ binder_select_thread_ilocked(struct binder_proc *proc)
return thread;
}
+/* CTABS: topology-aware selection with low-risk load-avoidance heuristic.
+ * Prefer threads with score: 3 (last_cpu == caller_cpu),
+ * 2 (same cluster and task_cpu == last_cpu), 1 (same cluster), else 0.
+ * This uses only thread->last_cpu and task_cpu() and falls back to FIFO.
+ */
+static struct binder_thread *
+binder_select_thread_topology_ilocked(struct binder_proc *proc, int caller_cpu)
+{
+ struct binder_thread *thread, *best = NULL;
+ int caller_cluster = -1;
+ int best_score = -1;
+
+ assert_spin_locked(&proc->inner_lock);
+
+ if ((unsigned int)caller_cpu < (unsigned int)nr_cpu_ids)
+ caller_cluster = topology_cluster_id(caller_cpu);
+
+ list_for_each_entry(thread, &proc->waiting_threads, waiting_thread_node) {
+ int tcpu = READ_ONCE(thread->last_cpu);
+ int score;
+
+ /* Highest priority: exactly the same CPU */
+ if (tcpu == caller_cpu && caller_cpu >= 0) {
+ best = thread;
+ goto found_best;
+ }
+
+ if (caller_cluster >= 0 && (unsigned int)tcpu < (unsigned int)nr_cpu_ids) {
+ /* Prefer threads that last ran on the same cluster */
+ if (topology_cluster_id(tcpu) == caller_cluster) {
+ score = 1;
+ goto check_score;
+ }
+ }
+ score = 0;
+
+check_score:
+ if (score > best_score) {
+ best_score = score;
+ best = thread;
+ }
+ }
+
+found_best:
+ if (best)
+ list_del_init(&best->waiting_thread_node);
+
+ return best;
+}
+
/**
* binder_wakeup_thread_ilocked() - wakes up a thread for doing proc work.
* @proc: process to wake up a thread in
@@ -672,9 +725,12 @@ static void binder_wakeup_thread_ilocked(struct binder_proc *proc,
static void binder_wakeup_proc_ilocked(struct binder_proc *proc)
{
- struct binder_thread *thread = binder_select_thread_ilocked(proc);
+ struct binder_thread *thread =
+ binder_select_thread_topology_ilocked(proc, raw_smp_processor_id());
+ if (!thread)
+ thread = binder_select_thread_ilocked(proc);
- binder_wakeup_thread_ilocked(proc, thread, /* sync = */false);
+ binder_wakeup_thread_ilocked(proc, thread, /* sync = */false);
}
static void binder_set_nice(long nice)
@@ -2877,6 +2933,12 @@ static int binder_proc_transaction(struct binder_transaction *t,
node->has_async_transaction = true;
}
+ /*
+ * Compute caller CPU before acquiring inner_lock to avoid
+ * doing CPU queries while holding spinlocks
+ */
+ int caller_cpu = raw_smp_processor_id();
+
binder_inner_proc_lock(proc);
if (proc->is_frozen) {
frozen = true;
@@ -2891,8 +2953,11 @@ static int binder_proc_transaction(struct binder_transaction *t,
return frozen ? BR_FROZEN_REPLY : BR_DEAD_REPLY;
}
- if (!thread && !pending_async)
- thread = binder_select_thread_ilocked(proc);
+ if (!thread && !pending_async) {
+ thread = binder_select_thread_topology_ilocked(proc, caller_cpu);
+ if (!thread)
+ thread = binder_select_thread_ilocked(proc);
+ }
if (thread) {
binder_enqueue_thread_work_ilocked(thread, &t->work);
@@ -4788,6 +4853,7 @@ static int binder_thread_read(struct binder_proc *proc,
}
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
+ WRITE_ONCE(thread->last_cpu, raw_smp_processor_id());
if (ret)
return ret;
@@ -5295,6 +5361,8 @@ static struct binder_thread *binder_get_thread_ilocked(
thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
thread->reply_error.cmd = BR_OK;
thread->ee.command = BR_OK;
+ /* CTABS: initialize last_cpu to -1 (unknown) */
+ WRITE_ONCE(thread->last_cpu, -1);
INIT_LIST_HEAD(&new_thread->waiting_thread_node);
return thread;
}
diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h
index 342574bfd28a..d01688cf0961 100644
--- a/drivers/android/binder_internal.h
+++ b/drivers/android/binder_internal.h
@@ -506,6 +506,8 @@ struct binder_thread {
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
+ /* CTABS: last CPU this thread ran on (smp_processor_id()), -1 if unknown */
+ int last_cpu;
};
/**
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] drivers: android: binder: add preferred CPU wake-up nudge for waiting threads
2026-07-14 18:35 [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection trieu2.huynh
2026-07-14 18:35 ` [PATCH 1/2] drivers: android: binder: implement CTABS heuristic trieu2.huynh
@ 2026-07-14 18:35 ` trieu2.huynh
2026-07-15 9:17 ` [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection Alice Ryhl
2 siblings, 0 replies; 5+ messages in thread
From: trieu2.huynh @ 2026-07-14 18:35 UTC (permalink / raw)
To: gregkh
Cc: arve, tkjos, brauner, cmllamas, aliceryhl, linux-kernel,
trieu2.huynh
Implement a wake-up nudge mechanism that hints the scheduler to wake up
the selected Binder thread on its preferred CPU. This maximizes cache
locality for the incoming transaction.
The preferred CPU is recorded during thread selection and stored in the
thread structure. Right before waking up the thread, this value is
safely injected into the task's 'wake_cpu' field.
Signed-off-by: trieu2.huynh <trieu2.huynh@lge.corp-partner.google.com>
---
drivers/android/binder.c | 39 +++++++++++++++++++++++++++++--
drivers/android/binder_internal.h | 4 ++--
2 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/drivers/android/binder.c b/drivers/android/binder.c
index 2a7be45368c1..580889b773b7 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -638,6 +638,7 @@ binder_select_thread_topology_ilocked(struct binder_proc *proc, int caller_cpu)
struct binder_thread *thread, *best = NULL;
int caller_cluster = -1;
int best_score = -1;
+ int chosen_cpu = -1;
assert_spin_locked(&proc->inner_lock);
@@ -648,9 +649,14 @@ binder_select_thread_topology_ilocked(struct binder_proc *proc, int caller_cpu)
int tcpu = READ_ONCE(thread->last_cpu);
int score;
- /* Highest priority: exactly the same CPU */
+ /* Highest priority: exactly the same CPU. No wake_cpu nudge
+ * needed here: __set_task_cpu() already keeps task->wake_cpu
+ * in sync with task_cpu(p) (== tcpu), so writing
+ * chosen_cpu = tcpu back would be a no-op.
+ */
if (tcpu == caller_cpu && caller_cpu >= 0) {
best = thread;
+ chosen_cpu = -1;
goto found_best;
}
@@ -667,12 +673,29 @@ binder_select_thread_topology_ilocked(struct binder_proc *proc, int caller_cpu)
if (score > best_score) {
best_score = score;
best = thread;
+ /* Nudge toward caller_cpu (not the thread's own stale
+ * tcpu) so the wake_cpu hint conveys new information:
+ * the transaction data was just touched on caller_cpu,
+ * and it shares an L2/L3 with tcpu within the cluster,
+ * so migrating there is cheap and cache-friendly.
+ */
+ if (score == 1 && caller_cpu >= 0) {
+ /* Only nudge if caller_cpu is idle */
+ if (available_idle_cpu(caller_cpu))
+ chosen_cpu = caller_cpu;
+ else
+ chosen_cpu = -1;
+ } else {
+ chosen_cpu = -1;
+ }
}
}
found_best:
- if (best)
+ if (best) {
list_del_init(&best->waiting_thread_node);
+ best->preferred_cpu = chosen_cpu;
+ }
return best;
}
@@ -700,6 +723,16 @@ static void binder_wakeup_thread_ilocked(struct binder_proc *proc,
assert_spin_locked(&proc->inner_lock);
if (thread) {
+#ifdef CONFIG_SMP
+ /* CTABS nudge: hint scheduler to wake thread on preferred CPU.
+ * This improves cache locality for the transaction. Only do
+ * this for sync wakeups, for oneway/async work the caller
+ * keeps running on caller_cpu.
+ */
+ if (sync && thread->preferred_cpu >= 0 && thread->task)
+ WRITE_ONCE(thread->task->wake_cpu, thread->preferred_cpu);
+#endif
+ thread->preferred_cpu = -1;
if (sync)
wake_up_interruptible_sync(&thread->wait);
else
@@ -5363,6 +5396,8 @@ static struct binder_thread *binder_get_thread_ilocked(
thread->ee.command = BR_OK;
/* CTABS: initialize last_cpu to -1 (unknown) */
WRITE_ONCE(thread->last_cpu, -1);
+ /* CTABS: initialize preferred_cpu to -1 (no nudge) */
+ WRITE_ONCE(thread->preferred_cpu, -1);
INIT_LIST_HEAD(&new_thread->waiting_thread_node);
return thread;
}
diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h
index d01688cf0961..285846e6068f 100644
--- a/drivers/android/binder_internal.h
+++ b/drivers/android/binder_internal.h
@@ -506,8 +506,8 @@ struct binder_thread {
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
- /* CTABS: last CPU this thread ran on (smp_processor_id()), -1 if unknown */
- int last_cpu;
+ int last_cpu; /* CTABS: last CPU this thread ran on */
+ int preferred_cpu; /* CTABS: preferred CPU for wake-up nudge */
};
/**
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection
2026-07-14 18:35 [PATCH 0/2] drivers: android: binder: Introduce Cluster Aware Thread Selection trieu2.huynh
2026-07-14 18:35 ` [PATCH 1/2] drivers: android: binder: implement CTABS heuristic trieu2.huynh
2026-07-14 18:35 ` [PATCH 2/2] drivers: android: binder: add preferred CPU wake-up nudge for waiting threads trieu2.huynh
@ 2026-07-15 9:17 ` Alice Ryhl
2 siblings, 0 replies; 5+ messages in thread
From: Alice Ryhl @ 2026-07-15 9:17 UTC (permalink / raw)
To: trieu2.huynh; +Cc: gregkh, arve, tkjos, brauner, cmllamas, linux-kernel
On Wed, Jul 15, 2026 at 03:35:50AM +0900, trieu2.huynh wrote:
> Motivation
> ----------
>
> On asymmetric multi-cluster ARM64 SoCs, the default binder thread
> selection is topology-blind. Cross-cluster thread wakeups incur severe
> cache-coherency overhead.
>
> Measured performance test [1], especially with schd-dbg tiered threads [2]
> on baseline (10K iter, 16B payload):
>
> Class Tier Avg Latency Count / % of txns
> ----- ---- ----------- -----------------
> SCHED_FIFO Same-core 0.089 ms 9,963 / 99.6%
> (max prio) Same-cluster 0.120 ms 13 / 0.1%
> Cross-cluster 0.140 ms 24 / 0.2%
> -----------------------------------------------------
> Normal Same-core 0.130 ms 4,623 / 46.2%
> (Other) Same-cluster 0.140 ms 3,290 / 32.9%
> Cross-cluster 0.190 ms 2,087 / 20.9%
>
> Key Observations:
> (*) For SCHED_FIFO tasks, the scheduler aggressively locks the affinity,
> keeping 99.6% of txns on the same core, establishing the hardware's
> ideal latency ceiling (~0.089 ms).
> (*) For normal tasks, selection is highly fragmented (only 46.2% on
> same-core). Cross-cluster transactions are ~46% slower (0.19 vs
> 0.13 ms) due to interconnect overhead and cache invalidation.
> (*) CTABS aims to close this gap for normal threads by enforcing affinity.
I've been told that GKI has patched wake_up_sync() to make it place the
recipient on the same CPU if nothing else is scheduled to run there.
* http://r.android.com/1362918
* http://r.android.com/1145589
Are these patches included in the tree from which you took this
benchmark?
Alice
^ permalink raw reply [flat|nested] 5+ messages in thread