Performance and memory
Reproduce foreground latency measurements and understand the native safety checks, their coverage, and their limits.
The foreground nbsp data command is the collector used by both opinionated
and detached Zsh. Git status refresh is deliberately outside that path: Zsh
starts nbsp refresh as a background worker and reloads prompt data after the
worker completes.
The repository does not ship a numerical performance baseline. Do not quote a latency, binary-size improvement, memory footprint, or instruction-count claim without retaining the raw report and enough build and machine metadata to reproduce the comparison.
Benchmark protocol
bench/benchmark.zsh measures a chosen command with one fresh nbsp process
per sample. Its defaults are 100 warm-up invocations and 1,000 measured
invocations. For example:
zsh bench/benchmark.zsh \
--warmup 100 \
--iterations 3000 \
--output ./data-benchmark.txt \
-- ./build/nbsp data
The harness supports data and dirs. It performs the requested warm-up
invocations first, times each measured invocation with Zsh’s EPOCHREALTIME,
redirects the command’s standard output, and calculates mean, minimum, maximum,
p50, and p95 from the sorted samples. Percentiles use the nearest-rank index.
The report also contains every sample as sample_sorted_ms; those values
cannot reconstruct temporal order. Because EPOCHREALTIME is a wall clock
rather than a monotonic clock, the harness rejects negative, non-finite, and
implausibly large intervals but cannot prevent smaller wall-clock adjustments
from affecting a result.
For data, the default representative inputs are status 17,
duration 2345 milliseconds, and two jobs. NBSP_BENCH_STATUS,
NBSP_BENCH_DURATION_MS, and NBSP_BENCH_JOBS select different inputs.
Iterations, warm-up, output, and working directory also have
NBSP_BENCH_ITERATIONS, NBSP_BENCH_WARMUP, NBSP_BENCH_OUTPUT, and
NBSP_BENCH_CWD defaults that their corresponding command-line flags override.
Iterations are limited to 100,000 and warm-up invocations to 100,000. The harness rejects an output symlink, special file, or pathname/inode that would replace the measured binary. It also discards the report if the binary’s version, size, or required SHA-256 changes during the run.
Format-2 reports use key=value lines. Text values use percent-byte encoding so
embedded whitespace, newlines, or = cannot create records. Numeric fields and
SHA-256 values remain plain ASCII. Each report records:
- start and end UTC timestamps,
unamedata, OS build, CPU brand when available, hardware model, and Zsh version; - the benchmark script’s SHA-256 plus the revision and dirty state of the checkout containing that script, when it is a Git repository;
- the binary’s absolute path, reported version, byte size, and mandatory SHA-256;
- caller-declared binary source revision/dirty state, build type, compiler, and
compiler/linker flags, each recorded as
unreportedunless supplied through the correspondingNBSP_BENCH_*variable; - the working directory, mode, shell-quoted invocation, warm-up count, and measured iteration count;
- a clean-environment workload repository root, HEAD, and coarse clean/dirty state when applicable;
NVM_BIN, cache-root inputs, and the effective cache-root candidate;- a SHA-256 of a NUL-framed observable workload snapshot captured before the warm-up and again after measurement; the report is discarded if those snapshots differ;
- the aggregate values and all sorted samples.
NBSP_BENCH_SOURCE_REVISION, NBSP_BENCH_SOURCE_DIRTY,
NBSP_BENCH_BUILD_TYPE, NBSP_BENCH_COMPILER, NBSP_BENCH_CFLAGS, and
NBSP_BENCH_LDFLAGS are declarations by the caller; the harness cannot prove
that an arbitrary selected binary came from its own checkout or from those
settings. The workload digest records the collector-visible data (or dirs
frame), not raw cache-file bytes or every filesystem attribute. Power and
thermal state, competing system load, and temporal sample order are also not
captured. Record and hold those conditions constant before comparing builds. A
warm repository and a non-repository directory remain different workloads.
Foreground and asynchronous work
The foreground collector starts neither Git nor Node. It still performs native
filesystem work: getcwd(3) in the CLI, physical-path canonicalization,
repository metadata discovery, direct HEAD reads, bounded cache reads and
validation, path abbreviation, and NVM_BIN parsing. Invoking it through Zsh
command substitution also starts the nbsp process itself.
The optional autosuggestion engine is separate from the prompt collector.
History lookup runs in Zsh. An eligible directory operand can start one
asynchronous nbsp dirs process for the resolved parent; that worker is bounded
to 50 milliseconds, 1,024 immediate child-directory records, and 64 KiB of
framed output. Moving this work off the keypress path does not make it free; it
changes when and where the work runs.
Implementation choices visible in the current source include:
- fixed-size buffers for repository discovery and direct
HEADreads; - a bounded cache file read followed by complete validation and an EOF check;
- one growable output buffer for prompt rendering, while the data command writes its framed records directly to the selected output stream;
- allocation-free NVM version extraction;
- a bounded Git child process with a clean repository-selection environment, output cap, timeout, and complete porcelain-v2 parse before publication;
- same-directory cache publication by
fsyncof the temporary file followed by atomic pathname replacement; - sorted, parent-keyed autosuggestion directory snapshots queried in Zsh without a filesystem syscall on each redraw.
These are implementation descriptions, not evidence of a speedup relative to an unspecified baseline.
Native safety checks
sh tests/run_memory_checks.sh requires Python 3.9 or newer and runs several
different checks:
- The configured analyzer compiler performs path-sensitive static analysis on every production C translation unit, including generated C that embeds both Zsh sources, and analyzer findings fail the run.
- A debug build runs the Meson test suite under AddressSanitizer and UndefinedBehaviorSanitizer with abort-on-first-error, strict string checks, and stack-use-after-return detection.
- A separate native debug build runs the same selected Meson tests. On Darwin, it additionally enables malloc scribbling, guard edges, and a heap check after every allocation operation.
- A deterministic ASan+UBSan mutation harness runs 250,000 iterations by default. It combines valid Git/cache/path/NVM seeds with mutated and random inputs up to 16 KiB, exercises all eight prompt-quoting and rendering option masks, and reaches the Git and cache parsers, codecs, path and NVM helpers, numeric parsing, prompt rendering, and buffer growth.
Interactive Zsh coverage is controlled by the Meson zsh_tests feature option:
auto(the default) registers the four Expect-backed tests whenexpectis available and otherwise omits them;enabledmakesexpecta required program at setup time;disableddoes not look forexpectand omits those tests.
The non-PTY Zsh syntax, data-protocol, and option-isolation tests remain registered independently. This matches Meson’s three-state feature-option semantics.
Standalone Meson setup defaults to auto. The comprehensive
run_memory_checks.sh runner instead defaults NBSP_ZSH_TESTS=enabled, so
Expect is required unless the caller explicitly selects auto or disabled.
When an unavailable optional suite is omitted, it is not registered; the runner
does not call that a skipped test.
Leak-detection boundaries
NBSP_ASAN_LEAKS=auto|enabled|disabled controls the memory-check runner’s leak
policy. In auto, the runner probes the selected ASan runtime with both a clean
program and an intentional leak, and requires the leak-specific diagnostic and
controlled exit status instead of inferring capability only from the operating
system. The test and fuzz builds are probed separately because they can resolve
different runtimes. enabled still probes and fails when support cannot be
demonstrated; disabled turns leak detection off explicitly. Ambient
LSAN_OPTIONS does not control these checks.
That distinction matters on macOS. Upstream Clang documents that its macOS runtime can enable leak detection, while Apple documents that Xcode’s Address Sanitizer does not detect memory leaks. The runner also leaves ASan initialization-order checking off on macOS because upstream Clang marks that option unsupported there.
On Darwin, the full native suite runs with malloc scribbling and guard edges; the native C unit binary then runs directly with per-allocation heap checks. These are corruption diagnostics, not leak detection. They do not replace LeakSanitizer, and the suite does not claim to measure process memory footprint. Passing these checks is evidence for the exercised inputs and configurations, not proof of memory safety or exhaustive parser coverage.
Architectural alternatives
The following designs are not enabled by default:
- Persistent Zsh coprocess: avoids starting a new foreground collector for each prompt, but adds framing, crash recovery, shell-exit cleanup, and stale process handling.
- Native Zsh module: avoids an external foreground process, but runs native code inside the shell and couples distribution to Zsh’s module interface.
- Per-shell repository memoization: can avoid some repeated discovery work, but needs explicit invalidation for changed, deleted, or replaced worktrees.
- Profile-guided optimization: requires a documented representative workload and retained profiles before any benefit can be claimed.
For a machine-local experiment, compiler-specific CPU tuning can be requested explicitly:
meson setup build-native --buildtype=release -Doptimization=3 \
-Db_lto=true -Dc_args=-mcpu=native
Such a binary targets the build machine’s CPU capabilities and should not be treated as a generally distributable artifact without a separate compatibility assessment.