Skip to content

Arm64: opt-in low-power spin-wait using FEAT_WFxT (WFET)#130362

Draft
EgorBo wants to merge 5 commits into
mainfrom
egorbo/arm64-wfxt-spinwait
Draft

Arm64: opt-in low-power spin-wait using FEAT_WFxT (WFET)#130362
EgorBo wants to merge 5 commits into
mainfrom
egorbo/arm64-wfxt-spinwait

Conversation

@EgorBo

@EgorBo EgorBo commented Jul 8, 2026

Copy link
Copy Markdown
Member

Closes #118933

Problem: Unlike x64 with its pause instruction, Arm64 has had to spin on a no-op instruction in a loop, loading a CPU core to 100% (on most, if not all, hardware the yield instruction is just an alias for nop). We even had to reduce the maximum spin-wait duration in .NET 9/10 to address the higher CPU usage some of our customers observed when they moved from x64 to Arm64.

Armv8.7 introduced WFxT (Wait For Event/Interrupt with Timeout), which can be used for this purpose. It is supported by Neoverse N3/V3 (Cobalt 200/Graviton5) and Apple M4+. Our CI has M4 in the Arm64 Helix queues.

I've disabled it by default (it will be enabled for CI only) so that we can first ask some 1P customers to check whether they observe any improvement/regression in any metric first.

Performance / benchmarks

Since the whole point is to reduce power usage while spinning, I validated the impact locally on an Apple M5 (which supports FEAT_WFxT), measuring per-task energy via the macOS task_info / TASK_POWER_INFO_V2 accounting. In every case the wall-clock time and CPU utilization are essentially unchanged — the spinning thread stays scheduled — but the energy it burns while spinning drops substantially:

  • A microbenchmark with several threads busy-waiting in Thread.SpinWait: roughly 5-6x lower energy (e.g. ~57 J → ~10 J over the same interval), with identical wall/CPU.
  • A thread-pool scenario with no explicit SpinWait, where worker threads spin on the pool's semaphore instead of parking (via a raised DOTNET_ThreadPool_UnfairSemaphoreSpinLimit): roughly 2x lower energy (~37 J → ~16 J), with CPU held constant.

One caveat worth stating explicitly: WFET reduces the power of spinning, not scheduler occupancy — the thread still holds its core for its time slice, it just idles the pipeline at low power. On server Arm (Neoverse), WFE/WFET can additionally release SMT/execution resources, so the tradeoff may be even more favorable there. These are illustrative local Apple M5 numbers; proper validation should be done on N3/V3-class server hardware.

Increasing the spin-wait duration

Because spinning becomes low-power, this also reverses the latency/energy tradeoff that forced us to reduce the maximum spin-wait duration in the first place (see the Problem section). With WFET we can afford to spin longer — keeping threads ready and avoiding the kernel wake round-trip (~2-10 µs), which lowers dispatch/lock latency — without paying the usual CPU/energy cost. Actually retuning those limits would be a separate, benchmark-driven follow-up (and, per the caveat above, still bounded under oversubscription).

References

Note

The grammar pass and the Performance/Increasing-spin-wait notes above were drafted with GitHub Copilot.

egorbo and others added 2 commits July 8, 2026 17:41
Add an opt-in, disabled-by-default low-power spin-wait for Arm64 based on the
FEAT_WFxT WFET (Wait For Event with Timeout) instruction.

When the DOTNET_ThreadWfetSpinWait knob is set to 1 and the hardware supports
FEAT_WFxT together with FEAT_ECV (for the self-synchronized CNTVCTSS_EL0 counter
read), YieldProcessorNormalized and its variants issue a single low-power WFET
timed wait for the equivalent duration instead of a busy YieldProcessor loop.
CPU utilization stays about the same (the thread remains scheduled) but energy
usage while spinning drops substantially.

Details:
* minipal detects FEAT_WFxT+FEAT_ECV (Linux HWCAP2_WFXT/HWCAP2_ECV, macOS
  sysctl hw.optional.arm.FEAT_WFxT/FEAT_ECV) as ARM64IntrinsicConstants_Wfxt,
  and provides minipal_wfet_wait_ns(). The WFET/CNTVCTSS/CNTFRQ/SB instructions
  are emitted via .inst so the sources build without an Armv8.7 assembler. The
  wait is scaled by CNTFRQ_EL0 (fixed 1GHz from Armv8.6 in the common case).
* The runtime enables the flag in EEJitManager::SetCpuInfo() when the knob is
  set and the feature is present. Non-Windows Arm64 only (WFET is emitted via
  GNU inline asm).
* When the knob is off (the default), behavior is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match Arm's recommended WFET delay sequence (nano_delay_v87 from the
"Multi-threaded applications" developer.arm.com blog) by comparing the elapsed
ticks against the requested duration (current - start < ticks) instead of an
absolute (current < target) comparison. This makes the wait loop robust to
system counter roll-over.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 16:10
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 8, 2026 16:10 — with GitHub Actions Failure
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in, low-power Arm64 spin-wait path that uses FEAT_WFxT (WFET) when supported (and gated on FEAT_ECV for CNTVCTSS_EL0 timing). The runtime enables it via a new config knob and uses the minipal implementation from YieldProcessorNormalized* hot paths.

Changes:

  • Add a new runtime-only Arm64 CPU feature flag (ARM64IntrinsicConstants_Wfxt) and detect it via Linux HWCAP2 / Apple sysctl.
  • Introduce minipal_wfet_wait_ns(...) + a runtime opt-in flag (g_minipalWfetSpinWaitEnabled) for non-Windows Arm64.
  • Add the ThreadWfetSpinWait config knob and wire it up in CoreCLR startup, then consume it in YieldProcessorNormalized*.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/native/minipal/cpufeatures.h Adds WFxT feature bit and declares WFET wait API + opt-in flag for non-Windows Arm64.
src/native/minipal/cpufeatures.c Detects FEAT_WFxT+ECV and implements the WFET timed-wait helper and opt-in flag storage.
src/coreclr/vm/codeman.cpp Enables WFET spin-wait at startup when supported and when ThreadWfetSpinWait is set.
src/coreclr/tools/Common/Compiler/HardwareIntrinsicHelpers.cs Reserves the WFxT bit to keep the bit layout synced with minipal (not mapped to an ISA).
src/coreclr/inc/yieldprocessornormalized.h Routes normalized yielding through the WFET wait when enabled.
src/coreclr/inc/clrconfigvalues.h Adds the ThreadWfetSpinWait opt-in config knob.

Comment thread src/native/minipal/cpufeatures.c Outdated
Comment thread src/native/minipal/cpufeatures.c
Comment thread src/coreclr/inc/yieldprocessornormalized.h
* minipal_wfet_wait_ns: read CNTFRQ_EL0 inline instead of caching it in a
  racy function-local static. CNTFRQ_EL0 is a constant register, so the
  per-call mrs is cheap and removes the data race on the shared cache.
* Use a 128-bit intermediate for the ns->ticks scaling so the multiply
  cannot overflow when CNTFRQ_EL0 is not 1GHz.
* TryYieldProcessorWithWfet: take 'unsigned int' instead of 'size_t' so that
  'normalizedYields * TargetNsPerNormalizedYield' cannot overflow the uint64_t
  result. All call sites already pass unsigned int.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 16:24
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 8, 2026 16:24 — with GitHub Actions Failure

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread src/native/minipal/cpufeatures.c
Comment thread src/native/minipal/cpufeatures.c
Copilot AI review requested due to automatic review settings July 8, 2026 18:58
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 8, 2026 18:58 — with GitHub Actions Failure

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/inc/clrconfigvalues.h Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 13:56
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 9, 2026 13:56 — with GitHub Actions Failure

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment on lines +86 to +88
#ifndef HWCAP2_ECV
#define HWCAP2_ECV (1 << 19)
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use FEAT_WFxT (armv8.7) for spin-waiting

2 participants