Performance and practical limits¶
Exact search scores every selected row. Budget for vector dimensions, metadata, temporary arrays, and your query rate. Validate representative workloads on your hardware before deciding whether an in-process collection fits.
Memory and workload shape¶
The active vector matrix alone needs rows × dimensions × 4 bytes. This excludes
private spare capacity, metadata objects, temporary arrays, and Python overhead.
Filtered searches allocate a gathered vector matrix, and raw Euclidean search
uses a float64 difference matrix. Concurrent searches multiply temporary memory.
Small top-k queries use partial selection before sorting the selected hits;
reducing top_k does not remove the full candidate metric calculation. Repeated
small additions reuse geometric capacity. Passing a batch still reduces Python
and validation overhead when inputs are already available together.
Published reference measurements¶
The following section is shared with the standalone package README. Its commit, machine, and dependency versions describe the measurement, not a new benchmark run of every later release.
VectorStore performs exact search over every selected row. The following
measurements are reference points, not latency guarantees:
Each cell shows the median time per cosine query followed by the stored vector matrix size:
| Rows | 384 dimensions | 1,536 dimensions | 3,072 dimensions |
|---|---|---|---|
| 1,000 | 0.032 ms · 1.5 MB | 0.049 ms · 6.1 MB | 0.088 ms · 12.3 MB |
| 10,000 | 0.286 ms · 15.4 MB | 1.065 ms · 61.4 MB | 2.122 ms · 122.9 MB |
| 100,000 | 2.983 ms · 153.6 MB | 9.877 ms · 614.4 MB | 20.509 ms · 1.23 GB |
These benchmarks intentionally stop at 100,000 rows. NumPy Vector Store is designed for small-to-medium, in-process exact search; 100,000 rows is an upper reference, not a promised limit or a target for continued scaling. The practical boundary depends on vector dimensions, metadata, available memory, and latency requirements. Workloads that routinely reach millions of vectors generally need an indexed or service-backed system.
These are unfiltered top_k=10 searches on a normalized store. Each row divides
the median duration of seven measured 20-query trials by 20, after two discarded
warmup trials. The vector matrix size excludes metadata, temporary search
arrays, and Python process overhead.
The measurements were taken from commit 4b23810 on a 24 GB Apple M4 Mac mini
with macOS 26.6.1, CPython 3.13.5, NumPy 2.3.3, and Accelerate BLAS. Hardware,
operating system activity, Python and NumPy versions, BLAS implementation, and
thread settings can all change the result.
The repository includes benchmark commands that emit the inputs, environment, raw samples, and median as JSON:
uv run python benchmarks/benchmark.py search \
--rows 10000 --dimensions 384 --queries 20 --top-k 10 \
--warmup 2 --repetitions 7 > /tmp/nvs-search.json
uv run python benchmarks/benchmark.py ingest \
--rows 10000 --dimensions 384 --batch-size 1 \
--warmup 2 --repetitions 7 > /tmp/nvs-ingest.json
For the same 10,000-by-384 prepared input, repeated single-row ingestion had a
median of 78.6 ms, or about 127,000 rows per second. Supplying 1,000 rows per
add() call had a median of 6.63 ms, or about 1.51 million rows per second.
Both measurements include construction of a fresh normalized store and every
add() call, but exclude input generation.
See the benchmark guide for the timed regions, complete options, output fields, and interpretation notes. The project keeps structural complexity checks in CI, but does not fail shared runners on wall-clock timing.