perf: fuse field multiply+reduce to eliminate ART wrapper overhead
On Android (ART), the unsignedMultiplyHigh wrapper function has per-call branching for API level detection that prevents ART's JIT from inlining the intrinsic into the hot loop. This creates ~10,000 extra function calls per signature verify (20 wrapper calls × 500 field muls). This commit introduces fieldMulReduce/fieldSqrReduce as expect/actual functions that fuse U256.mulWide + FieldP.reduceWide into a single compilation unit. The multiply-high intrinsic is passed as a crossinline lambda and inlined at each call site, producing platform-specific code with zero wrapper overhead: - Android API 35+: Math.unsignedMultiplyHigh inlined directly (UMULH) - Android API 31-34: Math.multiplyHigh + correction inlined (SMULH) - Android API <31: pure-Kotlin fallback inlined - JVM: Math.unsignedMultiplyHigh inlined directly - Native: pure-Kotlin fallback inlined The API level check happens ONCE per fieldMulReduce call (outermost branch) rather than per multiply-high call (innermost loop), so ART profiles and JIT-compiles only the hot path. https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY
This commit is contained in:
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils.secp256k1
|
||||
|
||||
/**
|
||||
* Android field multiply/square with API-level-gated intrinsics.
|
||||
*
|
||||
* Dispatches ONCE per call to an API-level-specific implementation. Each implementation
|
||||
* uses fieldMulReduceWith/fieldSqrReduceWith (inline) with the best available intrinsic
|
||||
* inlined directly at the call site. This eliminates the per-multiply-high wrapper call
|
||||
* overhead that hurts ART's JIT:
|
||||
*
|
||||
* Before: FieldP.mul → U256.mulWide → unsignedMultiplyHigh (branch) → Math.xxx
|
||||
* = 2 extra function calls per multiply-high × 20 per field mul = 40 calls
|
||||
*
|
||||
* After: FieldP.mul → fieldMulReduce → (one of the inlined paths)
|
||||
* = 1 function call total, Math.xxx is inlined directly into the loop
|
||||
*
|
||||
* The API level check is the outermost branch (not inside the hot loop), so ART
|
||||
* profiles and JIT-compiles only the hot path.
|
||||
*/
|
||||
|
||||
private val API = android.os.Build.VERSION.SDK_INT
|
||||
|
||||
@Suppress("NewApi")
|
||||
internal actual fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
if (API >= 35) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
} else if (API >= 31) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y ->
|
||||
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
|
||||
}
|
||||
} else {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NewApi")
|
||||
internal actual fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
if (API >= 35) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
} else if (API >= 31) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y ->
|
||||
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
|
||||
}
|
||||
} else {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user