Skip to main content

Performance Engineering

This directory records the measured performance contract and developer guidance for every implemented Softchat SDK language surface.

Refreshed audit identity

FieldValue
Host audit date2026-07-26
Android migration recheck2026-07-27
Frozen shared-SDK baselinebcf6dd71b88ea2c5033e5b3628f9ad28aa8eab0a
Final measured host revision5e7c0c65e6e86354624d64c9c89d32202e562a6c
Immutable host workflowPerformance Audit run 30186393765
Frozen Android legacy baselinea5edacccee3d62757a08dadeceeb3d29a4f4eac6
Android semantic AAR source27e135eac626951507bdeb623d7c56cf6fe88216
Android benchmark harness4a2989372af41e43d40c793f8674997336c83a58
Host process runs3 paired runs per revision and language
Android instrumentation runs3 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

SurfaceResultDecision
RustShared medians are within +1.35%, with 1-KiB encrypt 13.75% fasterNo latency regression
Kotlin/JVMIdentity/sign/parse and 64-KiB work improve or stay neutral; small crypto remains noisy/slowerAdvisory until JMH
Swift/macOSIdentity is 7.38% faster; crypto is neutral/faster; parse is +6.96% median, +12.49% p95Within descriptive budget
TypeScript/WasmEvery median is between -5.12% and +0.49%No latency regression
Physical AndroidEvery matched SDK median and p95 is faster than released KotlinPositive migration result
Android 64-KiB NIP-44Encrypt is -27.55% median/-43.07% p95; decrypt is -29.54%/-52.06%Audited Pixel passes 1.25x gate
FootprintThe complete protocol surface expands every artifact materiallyTrack as release work; do not hide growth
Physical iOSNo connected iOS device was availableExternal 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 publicKey in 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-bindgen ownership 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.

ArtifactOld bytesCurrent bytesDelta
Linux libsoftchat.so2,278,0483,956,496+73.68%
Linux libsoftchat.so.gz1,559,8132,196,812+40.84%
Linux libsoftchat.a30,290,49237,198,590+22.81%
Linux libsoftchat.a.gz8,764,21511,190,523+27.68%
Kotlin authored + generated JARs209,822773,170+268.49%
WebAssembly module1,636,5622,860,071+74.76%
WebAssembly module, compressed1,291,0871,675,061+29.74%
Swift harness dynamic-library set2,425,3364,895,304+101.84%
Android multi-ABI release AARn/a5,051,356Release-candidate baseline
npm packagen/a1,780,547Release-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:

MetricRolling-baseline budget
Median latency+10%
p95 latency+15%
PSS/RSS, allocations, startup, build time, energy, or sustained thermal cost+10%
Compressed artifact sizeFail 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

  1. Add Kotlin/JMH forks, warm-up/measurement iterations, Blackhole, allocation profiling, and a documented GC.
  2. Run the Android suite on one additional production device; isolate SDK and legacy PSS, startup, energy, and sustained thermal behavior.
  3. Run the packaged Swift facade on physical iOS and measure application startup, memory, archive size, and dSYMs.
  4. Measure real browser engines and Worker transfer/startup behavior.
  5. Evaluate dependency convergence and link-time footprint improvements with the full vector, package, and performance matrix.
  6. 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.