docs: document C interop benchmarks and K/N MUL limitation
Benchmarked two approaches for hardware 128-bit multiply on K/N: 1. Full mulWide via C interop (memScoped + allocArray + fe4_mul_reduce): FieldP.mul: 44ns → 116ns (2.6x SLOWER — copy/marshal overhead) 2. Per-call umulh via C interop (fe4_umulh, 20 calls per field mul): FieldP.mul: 44ns → 331ns (7.5x SLOWER — ~15ns bridge per call) Conclusion: K/N cinterop bridge adds ~15ns per call, making fine-grained C interop unviable for the multiply-high hot path (20+ calls per field op). The pure-Kotlin fused approach (4 IMUL per 128-bit product) remains optimal at 44ns/op until K/N supports hardware MUL natively. Updated FieldMulPlatform.native.kt docs with benchmarked rationale. Fixed remaining LongArray references in native benchmark test. https://claude.ai/code/session_01Sxi6Gpxbstuj3Y8TBY7XrU
This commit is contained in:
+7
-7
@@ -220,35 +220,35 @@ class Secp256k1NativeBenchmark {
|
||||
// out of the loop (dead code elimination risk with constant inputs).
|
||||
val a = U256.fromBytes(hexToBytes("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"))
|
||||
val b = U256.fromBytes(hexToBytes("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"))
|
||||
val out = LongArray(4)
|
||||
val w = LongArray(8)
|
||||
val out = Fe4()
|
||||
val w = Wide8()
|
||||
|
||||
val results = mutableListOf<BenchResult>()
|
||||
|
||||
// --- FieldP.mul (the hottest operation) ---
|
||||
// Uses `out` as both output and next input to create a data dependency chain.
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.mul", 5000, 100000) {
|
||||
FieldP.mul(out, out, b, w)
|
||||
}
|
||||
|
||||
// --- FieldP.sqr ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.sqr", 5000, 100000) {
|
||||
FieldP.sqr(out, out, w)
|
||||
}
|
||||
|
||||
// --- FieldP.add ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.add", 5000, 500000) {
|
||||
FieldP.add(out, out, b)
|
||||
}
|
||||
|
||||
// --- FieldP.sub ---
|
||||
a.copyInto(out)
|
||||
out.copyFrom(a)
|
||||
results +=
|
||||
bench("FieldP.sub", 5000, 500000) {
|
||||
FieldP.sub(out, out, b)
|
||||
@@ -277,7 +277,7 @@ class Secp256k1NativeBenchmark {
|
||||
printResults("secp256k1 Field Micro-Benchmarks: Kotlin/Native (LLVM AOT) on linuxX64", results)
|
||||
|
||||
// Use sinks to prevent dead code elimination of the entire benchmark
|
||||
assertTrue(hiSink != Long.MIN_VALUE || ltSink != Long.MIN_VALUE || out[0] != Long.MIN_VALUE)
|
||||
assertTrue(hiSink != Long.MIN_VALUE || ltSink != Long.MIN_VALUE || out.l0 != Long.MIN_VALUE)
|
||||
}
|
||||
|
||||
private fun hexToBytes(hex: String): ByteArray {
|
||||
|
||||
+18
-2
@@ -21,8 +21,24 @@
|
||||
package com.vitorpamplona.quartz.utils.secp256k1
|
||||
|
||||
/**
|
||||
* Native (iOS/macOS) fused field multiply/square using pure-Kotlin fallback.
|
||||
* Kotlin/Native compiles ahead-of-time, so inline + direct call is optimal.
|
||||
* Native fused field multiply/square using pure-Kotlin fallback.
|
||||
*
|
||||
* Uses fieldMulReduceFused which computes both lo and hi from shared 32-bit
|
||||
* sub-products (4 IMUL per 128-bit product instead of 5). This is the optimal
|
||||
* approach for Kotlin/Native because:
|
||||
*
|
||||
* 1. K/N has no way to emit the hardware MUL instruction (64×64→128) from
|
||||
* Kotlin code. Long * Long only gives the lower 64 bits (IMUL).
|
||||
*
|
||||
* 2. C interop was benchmarked and adds ~15ns per call through the K/N bridge
|
||||
* layer. With 20 multiply-high calls per field multiply, the bridge overhead
|
||||
* (~300ns) far exceeds the savings from hardware MUL (~150ns saved).
|
||||
*
|
||||
* 3. The fused mulFull approach (FieldMulFused.kt) computes both lo and hi
|
||||
* from 4 shared sub-products, saving 1 IMUL per product vs separate
|
||||
* lo (IMUL) + hi (4 IMUL fallback) = 5 IMUL.
|
||||
*
|
||||
* Measured: 44 ns/op on linuxX64 (pure Kotlin fused) vs 331 ns/op (C interop).
|
||||
*/
|
||||
internal actual fun fieldMulReduce(
|
||||
out: Fe4,
|
||||
|
||||
Reference in New Issue
Block a user