Zero-dependency PGM learned index in pure Rust: -99% RAM and 3x faster lookups.
Project details
A production-ready implementation of Piecewise Geometric Model (PGM) learned indexes in pure Rust. Instead of pointer-chasing through B-Tree node hierarchies, it models sorted key distributions with streaming linear regression within strict epsilon bounds. Lookups run in-register before a local L1 cache search, slashing RAM by 99% and cutting latency by 3x.
For decades, database indexing has relied on pointer-based tree structures (B-Tree, B+Tree, LSM-Tree). While mature, B-Trees hit physical hardware constraints on modern multi-core CPUs:
Learned Indexes reframe indexing as a regression problem: given sorted keys, their position corresponds to an empirical Cumulative Distribution Function (CDF).
learned_index_lab implements the Piecewise Geometric Model (PGM): it partitions sorted data into a minimal set of linear segments ($y = ax + b$) guaranteeing that the true key position lies strictly within a bounded error window $[y - \epsilon, y + \epsilon]$.
| Dimension | Standard BTreeMap | PGM-Index (Learned) | Concrete Hardware Impact |
|---|---|---|---|
| Index Structure | Dynamic pointer nodes | Contiguous array of segments | Entire index stays resident in L1/L2 cache |
| Lookup Mechanism | 4–6 node hops across RAM | Single in-register affine math + local search | Eliminates unpredictable pointer chasing |
| Build Time | $O(N \log N)$ repeated rebalancing | $O(N)$ single-pass streaming | ~22x faster construction |
| Memory Footprint | ~45 MB per 1,000,000 keys | 12 KB – 48 KB per 1,000,000 keys | -99.9% memory reduction |
Measured locally on x86_64 hardware across 10 randomized runs with 1,000,000 keys (64-bit integers) and 100,000 point queries per run:
Add learned_index_lab to your Cargo.toml:
use pgm::PgmIndex;
fn main() {
// 1. Prepare sorted keys
let data: Vec<u64> = vec![
100, 105, 110, 115, 120, 250, 500, 1000, 1500, 2000, 5000, 10000
];
// 2. Build the PGM index with maximum error epsilon (e.g. 16)
let pgm = PgmIndex::build(data, 16);
// 3. Exact point lookup (O(log S + log epsilon))
match pgm.find(1500) {
Some(index) => println!("Found key at index: {}", index),
None => println!("Key not found"),
}
// 4. Inspect memory overhead
println!("Total segments: {}", pgm.segments.len());
println!("Index memory: {} bytes", pgm.index_only_bytes());
}
Comments
0Start the conversation
Share the first comment.