perf: Android API-level-gated unsignedMultiplyHigh + eliminate per-call branch

Three Android-specific optimizations:

1. Use Math.unsignedMultiplyHigh on API 35+ (Android 15): single UMULH
   instruction, eliminates the 4-insn signed→unsigned correction that
   the fallback path requires. Same optimization as our JVM 18+ path.

2. Use Math.multiplyHigh + correction on API 31-34 (Android 12-14):
   avoids the pure-Kotlin 4×32-bit sub-product fallback entirely.

3. Resolve API level check ONCE at class load via static final fields
   (HAS_MULTIPLY_HIGH, HAS_UNSIGNED_MULTIPLY_HIGH) instead of checking
   Build.VERSION.SDK_INT on every call. These functions are called 16×
   per field multiply (~12,000× per signature verify), so eliminating
   the per-call branch matters.

Performance tiers on Android:
  API 35+ (Android 15):  ~same as JVM 18+ (UMULH intrinsic)
  API 31-34 (Android 12-14): SMULH + 3 correction insns per product
  API 26-30 (Android 8-11): pure-Kotlin fallback (4 sub-products)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
This commit is contained in:
Claude
2026-04-06 20:14:45 +00:00
parent 2bf911379f
commit e0fc42e955
@@ -21,14 +21,28 @@
package com.vitorpamplona.quartz.utils.secp256k1
/**
* Android implementation. Uses Math.multiplyHigh on API 31+ (Android 12),
* falls back to pure Kotlin on older devices.
* Android implementation with API-level-gated intrinsics.
*
* Resolved once at class init to avoid per-call branch overhead:
* - API 31+ (Android 12): Math.multiplyHigh (ART intrinsic → SMULH on ARM64)
* - API 35+ (Android 15): Math.unsignedMultiplyHigh (ART intrinsic → UMULH on ARM64)
* - Below: pure-Kotlin fallback via four 32-bit sub-products
*
* These functions are called 16× per field multiply (~12,000× per signature verify),
* so eliminating the per-call version check is critical.
*
* Implementation strategy is resolved once at class load time via static finals.
* The JIT then devirtualizes and inlines the hot path.
*/
private val HAS_MULTIPLY_HIGH = android.os.Build.VERSION.SDK_INT >= 31
private val HAS_UNSIGNED_MULTIPLY_HIGH = android.os.Build.VERSION.SDK_INT >= 35
internal actual fun multiplyHigh(
a: Long,
b: Long,
): Long =
if (android.os.Build.VERSION.SDK_INT >= 31) {
if (HAS_MULTIPLY_HIGH) {
Math.multiplyHigh(a, b)
} else {
multiplyHighFallback(a, b)
@@ -37,4 +51,14 @@ internal actual fun multiplyHigh(
internal actual fun unsignedMultiplyHigh(
a: Long,
b: Long,
): Long = unsignedMultiplyHighFallback(a, b)
): Long =
if (HAS_UNSIGNED_MULTIPLY_HIGH) {
@Suppress("NewApi")
Math.unsignedMultiplyHigh(a, b)
} else if (HAS_MULTIPLY_HIGH) {
// API 31-34: signed multiplyHigh + unsigned correction (3 extra insns)
Math.multiplyHigh(a, b) + (a and (b shr 63)) + (b and (a shr 63))
} else {
// API <31: pure-Kotlin fallback
unsignedMultiplyHighFallback(a, b)
}