perf: split Android fieldMulReduce into per-API methods for ART JIT
The previous approach inlined all 3 API-level branches into a single fieldMulReduce method (~600 DEX instructions). ART's register allocator produces suboptimal code for such large methods, causing stack spills that negate the benefit of eliminating wrapper calls. Split each API-level path into its own private function (~200 DEX instructions each). ART JIT-compiles only the hot one (fieldMulApi35 on API 35+) with full optimization, while the tiny dispatch function (fieldMulReduce) is easily devirtualized and inlined. https://claude.ai/code/session_01EMY5RnXb9rnsyU2KbXrSaY
This commit is contained in:
+75
-22
@@ -23,23 +23,51 @@ 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:
|
||||
* Each API level gets its OWN private function with the inline expansion,
|
||||
* so ART's JIT can fully optimize each as a standalone ~200 DEX-instruction
|
||||
* method. Previously, having all 3 branches inline-expanded in a single
|
||||
* function created ~600 DEX instructions, causing ART's register allocator
|
||||
* to produce suboptimal code with excessive stack spills.
|
||||
*
|
||||
* 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.
|
||||
* The dispatch functions (fieldMulReduce/fieldSqrReduce) are tiny (~15 DEX
|
||||
* instructions) and always take the same branch, so ART profiles and
|
||||
* devirtualizes them efficiently.
|
||||
*/
|
||||
|
||||
private val API = android.os.Build.VERSION.SDK_INT
|
||||
|
||||
// ==================== Multiply: separate methods per API level ====================
|
||||
|
||||
@Suppress("NewApi")
|
||||
private fun fieldMulApi35(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
}
|
||||
|
||||
private fun fieldMulApi31(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y ->
|
||||
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
|
||||
}
|
||||
}
|
||||
|
||||
private fun fieldMulFallback(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
b: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
|
||||
@Suppress("NewApi")
|
||||
internal actual fun fieldMulReduce(
|
||||
out: LongArray,
|
||||
@@ -48,16 +76,43 @@ internal actual fun fieldMulReduce(
|
||||
w: LongArray,
|
||||
) {
|
||||
if (API >= 35) {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
fieldMulApi35(out, a, b, w)
|
||||
} 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))
|
||||
}
|
||||
fieldMulApi31(out, a, b, w)
|
||||
} else {
|
||||
fieldMulReduceWith(out, a, b, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
fieldMulFallback(out, a, b, w)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Square: separate methods per API level ====================
|
||||
|
||||
@Suppress("NewApi")
|
||||
private fun fieldSqrApi35(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
}
|
||||
|
||||
private fun fieldSqrApi31(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y ->
|
||||
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
|
||||
}
|
||||
}
|
||||
|
||||
private fun fieldSqrFallback(
|
||||
out: LongArray,
|
||||
a: LongArray,
|
||||
w: LongArray,
|
||||
) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
}
|
||||
|
||||
@Suppress("NewApi")
|
||||
internal actual fun fieldSqrReduce(
|
||||
out: LongArray,
|
||||
@@ -65,12 +120,10 @@ internal actual fun fieldSqrReduce(
|
||||
w: LongArray,
|
||||
) {
|
||||
if (API >= 35) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> Math.unsignedMultiplyHigh(x, y) }
|
||||
fieldSqrApi35(out, a, w)
|
||||
} else if (API >= 31) {
|
||||
fieldSqrReduceWith(out, a, w) { x, y ->
|
||||
Math.multiplyHigh(x, y) + (x and (y shr 63)) + (y and (x shr 63))
|
||||
}
|
||||
fieldSqrApi31(out, a, w)
|
||||
} else {
|
||||
fieldSqrReduceWith(out, a, w) { x, y -> unsignedMultiplyHighFallback(x, y) }
|
||||
fieldSqrFallback(out, a, w)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user