perf: ARM64-specific optimizations for mobile phones

Optimize the ARM64 code path (the primary target for Nostr clients):

1. fe_mul_asm: Use LDP/STP (load/store pair) to load all 8 limbs in
   4 instructions instead of 8 individual LDR. Row 0 in hand-tuned
   ASM with MUL+UMULH+ADDS/ADC. Rows 1-3 in __int128 C (ARM64 gcc
   generates optimal MUL+UMULH+ADDS/ADCS and MADD from this).

2. fe_normalize: Branchless on ARM64 using mask-based conditional
   subtract (compiles to CSEL/AND on ARM64, avoiding branch
   misprediction on mobile Cortex-A76+ SoCs). x86_64 keeps the
   branching version since its branch predictor handles the >99.99%
   non-taken case perfectly.

3. Document ARM64-specific instruction usage:
   - MUL + UMULH: 64×64→128 product (1 cycle throughput on A76+)
   - LDP/STP: load/store pair (2 regs per instruction)
   - ADDS/ADCS: carry chain for accumulation
   - MADD: fused multiply-add (generated by gcc from __int128)

These changes don't affect x86_64 correctness or performance
(verified: all keys pass, benchmark matches previous numbers).
The ARM64 improvements will be measurable when built with the
Android NDK for actual phone testing.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
This commit is contained in:
Claude
2026-04-11 05:10:15 +00:00
parent 4fd4ad63d1
commit 57acbfd567
2 changed files with 86 additions and 39 deletions
+15 -1
View File
@@ -67,8 +67,21 @@ static inline int fe_is_odd(const secp256k1_fe *a) {
return (int)(t.d[0] & 1);
}
/* Normalize: if a >= p, subtract p. Inline for hot path. */
/* Normalize: if a >= p, subtract p.
* On ARM64: branchless (CSEL avoids misprediction on mobile SoCs).
* On x86_64: branching (>99.99% correct prediction, branch is cheaper). */
static inline void fe_normalize(secp256k1_fe *a) {
#if SECP_ARM64
/* Branchless for ARM64: compute mask, conditionally subtract */
uint64_t ge = (a->d[3] == UINT64_MAX) & (a->d[2] == UINT64_MAX) &
(a->d[1] == UINT64_MAX) & (a->d[0] >= FE_P0);
uint64_t mask = -(uint64_t)ge;
a->d[0] -= FE_P0 & mask;
a->d[1] &= ~mask;
a->d[2] &= ~mask;
a->d[3] &= ~mask;
#else
/* Branching for x86_64: branch predictor handles the >99.99% case */
if (a->d[3] == UINT64_MAX && a->d[2] == UINT64_MAX &&
a->d[1] == UINT64_MAX && a->d[0] >= FE_P0) {
a->d[0] -= FE_P0;
@@ -76,6 +89,7 @@ static inline void fe_normalize(secp256k1_fe *a) {
a->d[2] = 0;
a->d[3] = 0;
}
#endif
}
static inline void fe_normalize_full(secp256k1_fe *a) {