All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mark Rutland <mark.rutland@arm.com>
To: Ryan Roberts <ryan.roberts@arm.com>
Cc: Yang Shi <yang@os.amperecomputing.com>,
	cl@gentwo.org, dennis@kernel.org, tj@kernel.org,
	urezki@gmail.com, catalin.marinas@arm.com, will@kernel.org,
	david@kernel.org, akpm@linux-foundation.org, hca@linux.ibm.com,
	gor@linux.ibm.com, agordeev@linux.ibm.com, linux-mm@kvack.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org,
	Peter Zijlstra <peterz@infradead.org>,
	Ada Couprie Diaz <ada.coupriediaz@arm.com>
Subject: Re: [RFC v2 PATCH 0/16] Optimize this_cpu_*() ops for non-x86 (ARM64 for this series)
Date: Tue, 21 Jul 2026 20:08:20 +0100	[thread overview]
Message-ID: <al_DpFJFcmVhxpvW@J2N7QTR9R3> (raw)
In-Reply-To: <0344c559-1959-4531-9265-d5a5180eb7cd@arm.com>

On Thu, Jul 16, 2026 at 02:23:33PM +0100, Ryan Roberts wrote:
> We have observed a few performance regressions recently, for which the
> root cause is increased use of this_cpu_*. We have somebody at Arm
> about to start an investigation into whether in-kernel rseq can solve
> the problem.

FWIW, I had a look into something rseq like (resetting the PC upon
preemption or any exception), and I ended up with a GPR fixup scheme.
That avoids the need to disable/enable preemption, and only requires a
read of current and a couple of stores, which *should* be cheap.

The approach is similar to Peter Zijlstra's in-kernel rseq scheme [1],
and Heiko Carsten's approach for s390 [2]:

  [1] https://lore.kernel.org/lkml/20260223163843.GR1282955@noisy.programming.kicks-ass.net/
  [2] https://lore.kernel.org/lkml/20260526055702.1429061-1-hca@linux.ibm.com/

A key difference is that I ignore the PC entirely, and *only* track whether
GPRs are in use by a critical section. The GPR fixup is idempotent, and
can safely be applied anywhere in the critical section. Ignoring the PC
means that (in theory at least) this should work with kprobes, etc.

I've rebased and cleaned that up a bit, and pushed a WIP version to my
arm64/percpu-fixup branch:

  git://git.kernel.org/pub/scm/linux/kernel/git/mark/linux.git

I'll aim to have a more complete/polished version out in the near
future.

The key idea is that every percpu op has a critical section during which
it maintains three distinct GPRs:

  <pcp> : The original __percpu pointer.
  <off> : The percpu offset.
  <addr>: The final pointer (<pcp> + <off>).

Whenever the kernel performs an exception return back into a critical
section, it can fix up <off> and <addr>, as described below. The
critical section is roughly as follows:

	/*
	 * Prologue.
	 *
	 * At the start, <pcp> is already set, but <off> and <addr> are
	 * uninitialised.
	 *
	 * After this STR, upon an exception return into this critical
	 * section, the kernel will:
	 * 1. Update <off> to the current CPU's offset.
	 * 2. Update <addr> to be <pcp> + <off>.
	 */
	mrs	<tsk>, sp_el0
	mov	<tmp>, ENCODE_REGISTER_NUMBERS(<pcp>, <off>, <addr>)
	strh	<tmp>, [<tsk>, #TSK_TI_PCPU_GPRS]

	/*
	 * Generate the address. Any preemption within this critical
	 * section will result in <off> and <addr> being updated to
	 * match the CPU that this is resumed upon.
	 */
	mrs	<off>, TPIDR_ELx
	add	<addr>, <pcp>, <off>

	/* The actual operation, e.g. read/write/cmpxchg */
	DO_SOMETHING_WITH(<addr>)

	/*
	 * Epilogue.
	 *
	 * After this STR, the kernel will no longer apply the fixups.
	 */
	strh	wzr, [<tsk>, #TSK_TI_PCPU_GPRS]

To handle nesting, the exception code is updated to save/restore the
register numbers in pt_regs (and clearing the active value at entry) so
that this safely nests.

With that, a simple test case such as:

  u64 outline_this_cpu_read_u64(u64 __percpu *p)
  {
          return this_cpu_read(*p);
  }

... is reduced from:

  <outline_this_cpu_read_u64>:
         paciasp
         stp     x29, x30, [sp, #-32]!
         mrs     x1, sp_el0
         mov     x29, sp
         ldr     w2, [x1, #8]
         add     w2, w2, #0x1
         str     w2, [x1, #8]
         mrs     x2, tpidr_el1
         ldr     x0, [x0, x2]
         ldr     x2, [x1, #8]
         sub     x2, x2, #0x1
         str     w2, [x1, #8]
         cbz     x2, 1f
         ldr     x1, [x1, #8]
         cbnz    x1, 2f
  1:
         str     x0, [sp, #24]
         bl      0 <preempt_schedule_notrace>
         ldr     x0, [sp, #24]
  2:
         ldp     x29, x30, [sp], #32
         autiasp
         ret

... down to:

  <outline_this_cpu_read_u64>:
         mrs     x2, sp_el0        // Prologue
         mov     x4, #0xc80        // Prologue
         strh    w4, [x2, #20]     // Prologue
         mrs     x4, tpidr_el1
         add     x3, x0, x4
         ldr     x1, [x3]
         strh    wzr, [x2, #20]    // Epilogue
         mov     x0, x1
         ret

... which is clearly much better.

As noted above, the branch is a WIP. There are a bunch of things to do
(in particular, factoring out the xchg and cmpxchg assembly), but I
don't currently see a major technical blocker for the fixup approach.

Mark.

WARNING: multiple messages have this Message-ID (diff)
From: Mark Rutland <mark.rutland@arm.com>
To: Ryan Roberts <ryan.roberts@arm.com>
Cc: gor@linux.ibm.com, Peter Zijlstra <peterz@infradead.org>,
	catalin.marinas@arm.com, hca@linux.ibm.com,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org,
	urezki@gmail.com, Yang Shi <yang@os.amperecomputing.com>,
	cl@gentwo.org, david@kernel.org, tj@kernel.org,
	dennis@kernel.org, akpm@linux-foundation.org, will@kernel.org,
	agordeev@linux.ibm.com, linux-arm-kernel@lists.infradead.org
Subject: Re: [RFC v2 PATCH 0/16] Optimize this_cpu_*() ops for non-x86 (ARM64 for this series)
Date: Tue, 21 Jul 2026 20:08:20 +0100	[thread overview]
Message-ID: <al_DpFJFcmVhxpvW@J2N7QTR9R3> (raw)
In-Reply-To: <0344c559-1959-4531-9265-d5a5180eb7cd@arm.com>

On Thu, Jul 16, 2026 at 02:23:33PM +0100, Ryan Roberts wrote:
> We have observed a few performance regressions recently, for which the
> root cause is increased use of this_cpu_*. We have somebody at Arm
> about to start an investigation into whether in-kernel rseq can solve
> the problem.

FWIW, I had a look into something rseq like (resetting the PC upon
preemption or any exception), and I ended up with a GPR fixup scheme.
That avoids the need to disable/enable preemption, and only requires a
read of current and a couple of stores, which *should* be cheap.

The approach is similar to Peter Zijlstra's in-kernel rseq scheme [1],
and Heiko Carsten's approach for s390 [2]:

  [1] https://lore.kernel.org/lkml/20260223163843.GR1282955@noisy.programming.kicks-ass.net/
  [2] https://lore.kernel.org/lkml/20260526055702.1429061-1-hca@linux.ibm.com/

A key difference is that I ignore the PC entirely, and *only* track whether
GPRs are in use by a critical section. The GPR fixup is idempotent, and
can safely be applied anywhere in the critical section. Ignoring the PC
means that (in theory at least) this should work with kprobes, etc.

I've rebased and cleaned that up a bit, and pushed a WIP version to my
arm64/percpu-fixup branch:

  git://git.kernel.org/pub/scm/linux/kernel/git/mark/linux.git

I'll aim to have a more complete/polished version out in the near
future.

The key idea is that every percpu op has a critical section during which
it maintains three distinct GPRs:

  <pcp> : The original __percpu pointer.
  <off> : The percpu offset.
  <addr>: The final pointer (<pcp> + <off>).

Whenever the kernel performs an exception return back into a critical
section, it can fix up <off> and <addr>, as described below. The
critical section is roughly as follows:

	/*
	 * Prologue.
	 *
	 * At the start, <pcp> is already set, but <off> and <addr> are
	 * uninitialised.
	 *
	 * After this STR, upon an exception return into this critical
	 * section, the kernel will:
	 * 1. Update <off> to the current CPU's offset.
	 * 2. Update <addr> to be <pcp> + <off>.
	 */
	mrs	<tsk>, sp_el0
	mov	<tmp>, ENCODE_REGISTER_NUMBERS(<pcp>, <off>, <addr>)
	strh	<tmp>, [<tsk>, #TSK_TI_PCPU_GPRS]

	/*
	 * Generate the address. Any preemption within this critical
	 * section will result in <off> and <addr> being updated to
	 * match the CPU that this is resumed upon.
	 */
	mrs	<off>, TPIDR_ELx
	add	<addr>, <pcp>, <off>

	/* The actual operation, e.g. read/write/cmpxchg */
	DO_SOMETHING_WITH(<addr>)

	/*
	 * Epilogue.
	 *
	 * After this STR, the kernel will no longer apply the fixups.
	 */
	strh	wzr, [<tsk>, #TSK_TI_PCPU_GPRS]

To handle nesting, the exception code is updated to save/restore the
register numbers in pt_regs (and clearing the active value at entry) so
that this safely nests.

With that, a simple test case such as:

  u64 outline_this_cpu_read_u64(u64 __percpu *p)
  {
          return this_cpu_read(*p);
  }

... is reduced from:

  <outline_this_cpu_read_u64>:
         paciasp
         stp     x29, x30, [sp, #-32]!
         mrs     x1, sp_el0
         mov     x29, sp
         ldr     w2, [x1, #8]
         add     w2, w2, #0x1
         str     w2, [x1, #8]
         mrs     x2, tpidr_el1
         ldr     x0, [x0, x2]
         ldr     x2, [x1, #8]
         sub     x2, x2, #0x1
         str     w2, [x1, #8]
         cbz     x2, 1f
         ldr     x1, [x1, #8]
         cbnz    x1, 2f
  1:
         str     x0, [sp, #24]
         bl      0 <preempt_schedule_notrace>
         ldr     x0, [sp, #24]
  2:
         ldp     x29, x30, [sp], #32
         autiasp
         ret

... down to:

  <outline_this_cpu_read_u64>:
         mrs     x2, sp_el0        // Prologue
         mov     x4, #0xc80        // Prologue
         strh    w4, [x2, #20]     // Prologue
         mrs     x4, tpidr_el1
         add     x3, x0, x4
         ldr     x1, [x3]
         strh    wzr, [x2, #20]    // Epilogue
         mov     x0, x1
         ret

... which is clearly much better.

As noted above, the branch is a WIP. There are a bunch of things to do
(in particular, factoring out the xchg and cmpxchg assembly), but I
don't currently see a major technical blocker for the fixup approach.

Mark.


  reply	other threads:[~2026-07-21 19:08 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-15 18:04 [RFC v2 PATCH 0/16] Optimize this_cpu_*() ops for non-x86 (ARM64 for this series) Yang Shi
2026-07-15 18:04 ` [PATCH 01/16] drivers: arch_numa: move percpu set up code to arch Yang Shi
2026-07-15 18:04 ` [PATCH 02/16] arm64: kconfig: make percpu related configs not depend on NUMA Yang Shi
2026-07-15 18:04 ` [PATCH 03/16] mm: pgalloc: introduce {pud|pmd}_populate_sync() Yang Shi
2026-07-15 18:04 ` [PATCH 04/16] vmalloc: pass in pgd pointer for vmap{__vunmap}_range_noflush() Yang Shi
2026-07-15 18:04 ` [PATCH 05/16] arm64: mm: enable percpu kernel page table Yang Shi
2026-07-15 18:04 ` [PATCH 06/16] arm64: mm: defined {pud|pmd}_populate_sync() Yang Shi
2026-07-15 18:04 ` [PATCH 07/16] arm64: mm: sync percpu page table for memory hotplug/unplug Yang Shi
2026-07-15 18:04 ` [PATCH 08/16] arm64: kasan: sync up kasan shadow area page table Yang Shi
2026-07-15 18:04 ` [PATCH 09/16] arm64: mm: define percpu virtual space area Yang Shi
2026-07-15 18:04 ` [PATCH 10/16] mm: percpu: prepare to use dedicated percpu area Yang Shi
2026-07-15 18:04 ` [PATCH 11/16] arm64: mm: map local percpu first chunk Yang Shi
2026-07-15 18:04 ` [PATCH 12/16] mm: percpu: set up first chunk and reserve chunk Yang Shi
2026-07-15 18:04 ` [PATCH 13/16] arm64: mm: introduce __per_cpu_local_off Yang Shi
2026-07-15 18:04 ` [PATCH 14/16] mm: percpu: allocate and free local percpu vm area Yang Shi
2026-07-15 18:04 ` [PATCH 15/16] arm64: kconfig: select HAVE_LOCAL_PER_CPU_MAP Yang Shi
2026-07-15 18:04 ` [PATCH 16/16] arm64: percpu: use local percpu for this_cpu_*() APIs Yang Shi
2026-07-16 13:23 ` [RFC v2 PATCH 0/16] Optimize this_cpu_*() ops for non-x86 (ARM64 for this series) Ryan Roberts
2026-07-21 19:08   ` Mark Rutland [this message]
2026-07-21 19:08     ` Mark Rutland
2026-07-21 23:20   ` Yang Shi
2026-07-22  9:36     ` Mark Rutland

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=al_DpFJFcmVhxpvW@J2N7QTR9R3 \
    --to=mark.rutland@arm.com \
    --cc=ada.coupriediaz@arm.com \
    --cc=agordeev@linux.ibm.com \
    --cc=akpm@linux-foundation.org \
    --cc=catalin.marinas@arm.com \
    --cc=cl@gentwo.org \
    --cc=david@kernel.org \
    --cc=dennis@kernel.org \
    --cc=gor@linux.ibm.com \
    --cc=hca@linux.ibm.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=peterz@infradead.org \
    --cc=ryan.roberts@arm.com \
    --cc=tj@kernel.org \
    --cc=urezki@gmail.com \
    --cc=will@kernel.org \
    --cc=yang@os.amperecomputing.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.