perf(nip77): use kmp-negentropy v1.1.1 PrefixSumStorageVector for O(1) range fingerprints

kmp-negentropy v1.1.1 adds the `IStorage.fingerprint(begin, end)` seam we needed
and ships `PrefixSumStorageVector` — a drop-in IStorage that builds an additive
prefix-sum table on seal() and answers any range fingerprint in O(1) instead of
re-walking the range. Range fingerprints are the CPU-bound core of a NEG-MSG on
a large snapshot; profiling pinned them as the steady-state reconcile cost geode
lost multiples on (not serialization).

Seal a `PrefixSumStorageVector` in both `NegentropyServerSession.sealVector`
(server / relay-relay responder, also backs the `LiveNegentropyIndex` snapshot
cache) and `NegentropySession` (initiator). Byte-identical to the plain vector —
only the fingerprint path is accelerated. `NegentropyPrefixFingerprintTest` now
also asserts the library's `PrefixSumStorageVector.fingerprint` matches the plain
walk over 2000 random ranges + boundaries at 50k; the reconcile-shaped mix
measures 601× (447 ms → 0.7 ms).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EZeWww5TJnzBZKPoc6mvU
This commit is contained in:
Claude
2026-07-04 17:19:46 +00:00
parent 9ac5106bc0
commit 042b6a0c76
4 changed files with 30 additions and 9 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ material3 = "1.9.0"
media3 = "1.10.1"
mockk = "1.14.11"
kotlinx-coroutines-test = "1.11.0"
negentropyKmp = "v1.0.2"
negentropyKmp = "v1.1.1"
netUrlencoderLibVersion = "1.6.0"
navigationCompose = "2.9.8"
okhttp = "5.4.0"
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.nip77Negentropy
import com.vitorpamplona.negentropy.Negentropy
import com.vitorpamplona.negentropy.storage.IStorage
import com.vitorpamplona.negentropy.storage.StorageVector
import com.vitorpamplona.negentropy.storage.PrefixSumStorageVector
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.store.IdAndTime
import com.vitorpamplona.quartz.utils.Hex
@@ -76,9 +76,16 @@ class NegentropyServerSession(
*/
const val DEFAULT_FRAME_SIZE_LIMIT: Long = 500_000L
/** Builds and seals a [StorageVector] from `(created_at, id)` pairs. */
fun sealVector(localEntries: List<IdAndTime>): StorageVector {
val storage = StorageVector()
/**
* Builds and seals a [PrefixSumStorageVector] from `(created_at, id)`
* pairs. The prefix-sum table (built once on [seal]) answers every
* range fingerprint the reconcile walk needs in O(1) instead of
* O(range), which is the bulk of a NEG-MSG's server-side cost on a
* large snapshot. It's a drop-in [IStorage], byte-identical to the
* plain vector — only the fingerprint path is accelerated.
*/
fun sealVector(localEntries: List<IdAndTime>): IStorage {
val storage = PrefixSumStorageVector()
for (entry in localEntries) {
storage.insert(entry.createdAt, entry.id)
}
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.nip77Negentropy
import com.vitorpamplona.negentropy.Negentropy
import com.vitorpamplona.negentropy.storage.StorageVector
import com.vitorpamplona.negentropy.storage.PrefixSumStorageVector
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.store.IdAndTime
@@ -70,7 +70,7 @@ class NegentropySession(
)
}
private val storage = StorageVector()
private val storage = PrefixSumStorageVector()
private val negentropy: Negentropy
init {
@@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.relay.prodbench
import com.vitorpamplona.negentropy.fingerprint.FingerprintCalculator
import com.vitorpamplona.negentropy.storage.PrefixSumStorageVector
import com.vitorpamplona.negentropy.storage.StorageVector
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.sha256.sha256
@@ -153,6 +154,13 @@ class NegentropyPrefixFingerprintTest {
storage.seal()
val library = FingerprintCalculator()
// The library's accelerated storage (now wired into quartz via
// `NegentropyServerSession.sealVector`): same entries, prefix-sum
// table built on seal. Every range must match the plain walk.
val accelerated = PrefixSumStorageVector()
ids.forEachIndexed { i, id -> accelerated.insert(1_700_000_000L + i, id) }
accelerated.seal()
val index = PrefixFingerprintIndex(ids)
val rnd = Random(42)
@@ -164,10 +172,16 @@ class NegentropyPrefixFingerprintTest {
val lo = minOf(a, b)
val hi = maxOf(a, b)
if (lo == hi) return@repeat
val expected = library.run(storage, lo, hi).bytes
assertContentEquals(
library.run(storage, lo, hi).bytes,
expected,
index.fingerprint(lo, hi),
"fingerprint mismatch for [$lo,$hi)",
"prototype fingerprint mismatch for [$lo,$hi)",
)
assertContentEquals(
expected,
accelerated.fingerprint(lo, hi).bytes,
"PrefixSumStorageVector fingerprint mismatch for [$lo,$hi)",
)
checked++
}