perf: fe_sqr calls fe_mul — eliminates 5ns/sqr gap, 33% faster fe_inv

fe_sqr was 20.6ns (using mul_wide + reduce_wide as separate functions)
while fe_mul was 15.6ns (inlined). Simply making fe_sqr call fe_mul
eliminates the gap.

This has a massive impact on fe_inv/fe_sqrt which do 255 squarings:
  fe_inv:  6107ns → 4085ns (33% faster!)
  fe_sqr:  20.6ns → 15.8ns (23% faster)
  fe_mul:  15.6ns → 14.9ns (stable)

Impact on operations:
  sign (cached):     15.2µs → 13.6µs (1.30x faster than ACINQ)
  pubkeyCreate:      15.3µs → 14.1µs (1.24x faster)
  verifyFast:        35.1µs → 32.2µs (1.01x vs ACINQ — tied!)
  verify (BIP-340):  39.7µs → 36.5µs (0.89x vs ACINQ)
  batch(200)/event:   6.2µs →  4.5µs (8.3x faster than ACINQ!)

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
This commit is contained in:
Claude
2026-04-12 02:52:51 +00:00
parent 070affb4e4
commit 854bf9379a
+6 -28
View File
@@ -200,36 +200,14 @@ void fe_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe *b) {
}
/*
* Dedicated squaring: exploits a[i]*a[j] == a[j]*a[i] to halve cross-products.
* 4x4 squaring needs only 10 products vs 16 for general multiplication:
* Diagonal: a0², a1², a2², a3² (4 products)
* Cross: a0*a1, a0*a2, a0*a3, a1*a2, a1*a3, a2*a3 (6 products, doubled)
* Squaring: just call fe_mul(r, a, a).
* With 4x64 limbs, a dedicated sqr doesn't help because:
* - Cross-product doubling overflows uint128 (64+64+1 > 128 bits)
* - fe_mul is already inlined with optimal instruction scheduling
* - Saves 0 products (still 16 MUL instructions either way)
*/
void fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) {
uint64_t a0 = a->d[0], a1 = a->d[1], a2 = a->d[2], a3 = a->d[3];
uint64_t w[8];
#if HAVE_INT128
uint128_t cross, diag, acc;
/* Compute cross-products first (each appears twice) */
/* w[1] = 2*a0*a1 */
/* w[2] = 2*a0*a2 + a1*a1 */
/* w[3] = 2*a0*a3 + 2*a1*a2 */
/* w[4] = 2*a1*a3 + a2*a2 */
/* w[5] = 2*a2*a3 */
/* Use mul_wide for correctness. The "add twice" approach for cross products
* can overflow uint128 when a[i] values are near 2^64.
* A dedicated sqr_wide requires 192-bit intermediate tracking to handle
* the doubled cross products safely. For now, mul_wide is proven correct. */
mul_wide(w, a->d, a->d);
#else
/* Fallback: use general multiplication */
mul_wide(w, a->d, a->d);
#endif
reduce_wide(r, w);
fe_mul(r, a, a);
}
#else /* Portable fallback */