Performance Engineering
This directory records the measured performance contract and developer guidance for every implemented Softchat SDK language surface.
Refreshed audit identity
| Field | Value |
|---|---|
| Host audit date | 2026-07-26 |
| Android migration recheck | 2026-07-27 |
| Frozen shared-SDK baseline | bcf6dd71b88ea2c5033e5b3628f9ad28aa8eab0a |
| Final measured host revision | 5e7c0c65e6e86354624d64c9c89d32202e562a6c |
| Immutable host workflow | Performance Audit run 30186393765 |
| Frozen Android legacy baseline | a5edacccee3d62757a08dadeceeb3d29a4f4eac6 |
| Android semantic AAR source | 27e135eac626951507bdeb623d7c56cf6fe88216 |
| Android benchmark harness | 4a2989372af41e43d40c793f8674997336c83a58 |
| Host process runs | 3 paired runs per revision and language |
| Android instrumentation runs | 3 runs of 30 AndroidX measurements |
The Android result measures the exact packaged boundary used by the application migration candidate. Its harness revision is recorded separately because benchmark isolation changes do not alter the semantic AAR.
Outcome
| Surface | Result | Decision |
|---|---|---|
| Rust | Shared medians are within +1.35%, with 1-KiB encrypt 13.75% faster | No latency regression |
| Kotlin/JVM | Identity/sign/parse and 64-KiB work improve or stay neutral; small crypto remains noisy/slower | Advisory until JMH |
| Swift/macOS | Identity is 7.38% faster; crypto is neutral/faster; parse is +6.96% median, +12.49% p95 | Within descriptive budget |
| TypeScript/Wasm | Every median is between -5.12% and +0.49% | No latency regression |
| Physical Android | Every matched SDK median and p95 is faster than released Kotlin | Positive migration result |
| Android 64-KiB NIP-44 | Encrypt is -27.55% median/-43.07% p95; decrypt is -29.54%/-52.06% | Audited Pixel passes 1.25x gate |
| Footprint | The complete protocol surface expands every artifact materially | Track as release work; do not hide growth |
| Physical iOS | No connected iOS device was available | External gate remains |
Detailed reports:
The developer site presents the same accepted structured data as an interactive performance analysis with relative-gap, tail-amplification, and evidence-coverage views.
This is the refreshed performance checkpoint for the complete protocol surface. It is a partial audit, not the first rolling release baseline: Kotlin/JMH, a second Android device, physical iOS, and calibrated resource measurements remain open.
Measurement contract
The harnesses call authored public APIs and include language adaptation plus FFI/Wasm overhead. They do not benchmark private cipher functions and present that as application latency.
- Shared inputs use fixed synthetic identities and payloads of 32, 1,024, and 64,000 UTF-8 bytes.
- Each process warms every operation and then records 20 calibrated samples.
- Baseline/current process order alternates across three independent paired runs.
- Reports use the median of the three run medians and the median of the three run p95 values.
- Android uses the same aggregation across three independent release instrumentation invocations and also records allocation counts.
- Negative deltas mean the current implementation is faster.
- Cached getters below the clock's useful resolution are API-shape checks.
- NIP-17, NIP-59, and streaming-attachment operations have no old-SDK equivalent and establish current-only descriptive baselines.
Absolute latency is not comparable across Rust, JVM, Swift, Wasm, Android, or iOS. Compare old and current only within one language, workload, runner/device class, toolchain, and build profile.
Optimizations implemented
Shared/native
- Keep cryptography, complete event/envelope work, Noise, Negentropy, relay state transitions, and media transforms as coarse Rust operations.
- Compile private UniFFI/JNI adapters only for native artifacts and omit native-only validation/handle exports from semantic-only and Wasm builds.
- Return immutable imported public metadata once instead of repeatedly deriving or crossing FFI.
- Compile the weak JNI identity registry only for Android JNI builds; other targets no longer allocate or lock Android-only state.
- Stream attachment encryption/decryption in bounded chunks and withhold decrypted output until final authentication.
Kotlin and Android
- Cache
publicKeyin the authored identity. - Retain efficient JVM crossing choices for small and large payload classes.
- On Android, bypass generic JNA string/record copies with one narrow JNI byte-array bridge while preserving the authored Kotlin API.
- Strip release AAR libraries, retain separate symbols, preserve 16-KiB page alignment, and verify an R8-minified external consumer.
Swift
- Import through the lightweight native constructor, cache the public key, and avoid the generic combined-record import.
- Use direct payload fields for crypto while keeping generated records internal.
- Preserve deterministic identity erasure through the existing lock without adding Android registry state.
TypeScript/Wasm
- Share exactly one Wasm module across CommonJS, Node ESM, browser, and Worker exports.
- Keep the authored facade in domain modules with a small public barrel; the browser build redirects only the raw Wasm import instead of maintaining a second implementation.
- Cache imported public metadata in the authored object.
- Return frozen plain JavaScript values and keep
wasm-bindgenownership internal.
No optimization caches plaintext, private keys, derived conversation keys, or unauthenticated envelope layers.
Android migration decision
The old debug comparison reported a 2.27x/2.34x 64-KiB regression. The replacement non-debuggable AndroidX target showed that native Rust was not the bottleneck; generic string FFI dominated the boundary. The JNI byte-array facade fixes that conversion cost and reduces measured crypto allocations from about 2,600 per legacy call to 5-7 per SDK call.
The Pixel 9 Pro Fold now satisfies both median and p95 requirements for 64-KiB encrypt and decrypt by at least 27.55% at the median and 43.07% at p95. The requirement still names two devices, so the second production-build device remains a release gate.
Footprint
Sizes are deterministic build artifacts, not installed application size.
Compressed native/Wasm values use gzip -n.
| Artifact | Old bytes | Current bytes | Delta |
|---|---|---|---|
Linux libsoftchat.so | 2,278,048 | 3,956,496 | +73.68% |
Linux libsoftchat.so.gz | 1,559,813 | 2,196,812 | +40.84% |
Linux libsoftchat.a | 30,290,492 | 37,198,590 | +22.81% |
Linux libsoftchat.a.gz | 8,764,215 | 11,190,523 | +27.68% |
| Kotlin authored + generated JARs | 209,822 | 773,170 | +268.49% |
| WebAssembly module | 1,636,562 | 2,860,071 | +74.76% |
| WebAssembly module, compressed | 1,291,087 | 1,675,061 | +29.74% |
| Swift harness dynamic-library set | 2,425,336 | 4,895,304 | +101.84% |
| Android multi-ABI release AAR | n/a | 5,051,356 | Release-candidate baseline |
| npm package | n/a | 1,780,547 | Release-candidate baseline |
The old SDK exposed a small initial surface; current artifacts include chat and account profiles, relay/session/delivery, Noise, Negentropy, NIP-19/42/98, external signing, diagnostics, and streaming media. Growth is therefore expected but not exempt from review. The Swift row is an unstripped harness input, not final app size. Android ships stripped libraries and a separate symbols archive.
Baseline and release policy
The frozen historical baselines are never silently advanced. New operations use their first accepted optimized measurement as a capability baseline. After the complete device/resource audit, its accepted revision becomes the first rolling release baseline.
Automatic gates require five unchanged-revision invocations on a stable runner/device class. Runner variation is the largest relative deviation of the five run medians or p95 values from their respective median. The allowed regression is the budget below or three times that variation, whichever is larger:
| Metric | Rolling-baseline budget |
|---|---|
| Median latency | +10% |
| p95 latency | +15% |
| PSS/RSS, allocations, startup, build time, energy, or sustained thermal cost | +10% |
| Compressed artifact size | Fail only when growth exceeds both 5% and 100 KiB |
One automatic retry must reproduce a latency or resource failure before it blocks. An exception records reason, owner, target, risk, and an expiry or review milestone.
Kotlin's current plain loop is not eligible for these gates. The one-device Android result is valid migration evidence but is not a calibrated device-class gate.
Remaining performance and release work
- Add Kotlin/JMH forks, warm-up/measurement iterations,
Blackhole, allocation profiling, and a documented GC. - Run the Android suite on one additional production device; isolate SDK and legacy PSS, startup, energy, and sustained thermal behavior.
- Run the packaged Swift facade on physical iOS and measure application startup, memory, archive size, and dSYMs.
- Measure real browser engines and Worker transfer/startup behavior.
- Evaluate dependency convergence and link-time footprint improvements with the full vector, package, and performance matrix.
- Provision stable runners/devices, calibrate five unchanged-revision runs, and promote eligible metrics to rolling release gates.
Outside the performance workstream, release readiness still requires released-client and Softrelay round trips, Room/GRDB/IndexedDB adapters, process-death/migration tests, registry publication, signing, SBOM, provenance, and reversible app cutovers. The canonical list is the support and maturity page.
Updating the audit
Rerun every language, affected device, correctness vector, and artifact size after shared crypto, serialization, payload-limit, dependency, FFI-shape, or native-profile changes. Rerun an affected language and its package/device consumer after facade or packaging changes. Documentation-only changes do not require another performance run.
Record exact revisions, toolchains, build modes, sanitized runner/device class,
commands or immutable workflow URL, run count, and any deviation. Keep raw CI
artifacts transient until the complete P1 matrix exists. The first durable
accepted bundle belongs under
docs/performance/evidence/<date>-<current-short-sha>/ and must omit device
serials, usernames, credentials, production data, binaries, APKs, libraries,
and full system logs.
See the Performance Benchmarks source guide for commands and JSON schemas.