benchmark comparison
altor-vec vs Lance
Columnar/vector data platform tradeoffs versus browser-native ANN.
Lance is not just an ANN library; it sits closer to a data format and platform story. That makes it powerful for data-heavy workflows, but it also means it is solving a broader problem than a tiny browser vector dependency.
Comparison table
| Category | altor-vec | Lance |
|---|---|---|
| Runtime model | In-browser ANN runtime for shipped apps. | Vector-aware data platform and file-format ecosystem used in data and backend workflows. |
| Bundle size / delivery | ~54KB gzipped library payload. | Broader integration footprint because the value proposition goes beyond a tiny browser bundle. |
| Query latency | Interactive local lookup in the browser. | Good performance in data-platform contexts, but not optimized around the same frontend-delivery constraint. |
| Memory usage | Browser memory is the main limit. | Designed for larger data workflows where storage and access patterns differ from a shipped web asset. |
| Features | Focused ANN search and serialization. | Broader table, storage, and vector ecosystem capabilities. |
| Dataset sweet spot | Curated corpora bundled into web apps. | Teams working with larger vector datasets and data-engineering style workflows. |
Where altor-vec wins
- Much simpler when the goal is just local browser retrieval.
- Smaller payload and fewer moving parts.
- No data-platform adoption step required.
Where Lance wins
- Broader data workflow story.
- Better fit for teams treating vectors as part of a larger analytical dataset.
- Stronger option when browser delivery is not the main constraint.
Honest decision guide
Lance is more compelling when you are building a vector-aware data stack. altor-vec is more compelling when you need a frontend feature with minimal overhead.
The honest pattern across all of these benchmark pages is simple: if the search corpus should stay on the server, choose server-oriented infrastructure. If the search corpus is intentionally shipped with the product and the UX benefit of local retrieval matters more than backend scale, altor-vec is usually the more natural fit.
FAQ
Is Lance overkill for browser-only search?
It can be, depending on your needs. If you just want a small client-side ANN layer, a narrow library is often easier to justify.
When does Lance make more sense?
When vectors are part of a broader data storage and pipeline story rather than just a frontend capability.
What is altor-vec's advantage here?
Low-friction delivery for app-embedded search.
Get started: npm install altor-vec · GitHub
Benchmark methodology
These measurements reflect altor-vec running in a controlled browser environment. All queries execute against a pre-built HNSW index loaded from a JSON file — no embedding generation time is included. Embeddings are generated once at build time.
| Parameter | Value |
|---|---|
| Index size | 10,000 vectors |
| Dimensions | 384 (all-MiniLM-L6-v2) |
| HNSW M | 16 |
| ef_construction / ef_search | 200 / 50 |
| k | 5 |
| Browser | Chrome 124, M2 MacBook Pro |
| Measurement | p50/p95 of 1,000 consecutive queries |
altor-vec latency (10K × 384d)
| Metric | Result |
|---|---|
| p50 query latency | 0.4ms |
| p95 query latency | 0.8ms |
| Index load time | ~35ms |
| Memory footprint | ~17MB |
| WASM bundle size | 54KB gzipped |
What these numbers mean
Sub-millisecond latency means search is effectively instant from the user's perspective. Human perception of "instantaneous" begins around 100ms — altor-vec at p95 (0.8ms) is 125× faster than a cloud search call at 100ms total round-trip.
The 17MB footprint for 10K vectors fits easily in modern browser memory. For 100K vectors at 384 dimensions, expect ~170MB — viable on desktop, worth testing on mobile.
Run your own benchmark
import init, { WasmSearchEngine } from 'altor-vec';
await init();
const engine = WasmSearchEngine.from_vectors(vectors, DIM, 16, 200, 50);
const query = new Float32Array(DIM);
const times = [];
for (let i = 0; i < 1000; i++) {
const t = performance.now();
engine.search(query, 5);
times.push(performance.now() - t);
}
times.sort((a, b) => a - b);
console.log('p50:', times[500].toFixed(2) + 'ms');
console.log('p95:', times[950].toFixed(2) + 'ms');