mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 16:57:39 +00:00
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:
@@ -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"
|
||||
|
||||
+11
-4
@@ -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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+16
-2
@@ -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++
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user