# Performance Engineering

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

## Refreshed audit identity

| Field | Value |
| --- | --- |
| Date | 2026-07-26 |
| Frozen shared-SDK baseline | `bcf6dd71b88ea2c5033e5b3628f9ad28aa8eab0a` |
| Final measured host revision | `5e7c0c65e6e86354624d64c9c89d32202e562a6c` |
| Immutable host workflow | [Performance Audit run 30186393765](https://github.com/jacklnem/softchat/actions/runs/30186393765) |
| Frozen Android legacy baseline | `a5edacccee3d62757a08dadeceeb3d29a4f4eac6` |
| Android benchmark build | `00b9cd857b5ed586fa1fce2efa95c845b47ec7a3` |
| Host process runs | 3 paired runs per revision and language |
| Android instrumentation runs | 3 runs of 30 AndroidX measurements |

The Android benchmark predates host-only Swift/identity and documentation
changes. Its shared runtime is `472bdc2`; the later main-branch changes preserve
the measured Android JNI path. The minified AAR consumer was rebuilt after the
optimization work.

## 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 SDK median is faster than released Kotlin | Positive migration result |
| Android 64-KiB NIP-44 | Encrypt is -7.16% median/-9.11% p95; decrypt is -18.54%/-9.17% | Audited Pixel passes 1.25x gate |
| Footprint | Full Phase 2 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:

- [Rust](rust.md)
- [Kotlin/JVM](kotlin.md)
- [Swift](swift.md)
- [TypeScript and WebAssembly](typescript.md)
- [Android device comparison](android.md)

This is the refreshed performance checkpoint for
[Phase 2](../ideas/phase-2-softchat-parity.md). 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.
- 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.
- 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. 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 Phase 1 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

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, Phase 2 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.

## 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 [Performance Benchmarks](../../benchmarks/README.md) for commands and JSON
schemas.
