Date: 2025-11-10
Commit: 39b727f (PrefixZipper initial implementation)
Hardware: Intel Xeon E5-2699 v3 @ 2.30GHz (36 cores, 72 threads), 252GB RAM
Compiler: rustc with -C target-cpu=native optimizations
This document establishes the performance baseline for the PrefixZipper implementation before any optimizations are applied. All measurements follow the scientific method: measure first, optimize second.
Dictionary Sizes: 1K, 10K, 100K terms Prefix Selectivity Scenarios:
Backends Tested:
Benchmark: prefix_selectivity/high_selectivity/5
Time: 1.66 µs [1.6517 µs 1.6599 µs 1.6689 µs]
Throughput: ~602,000 ops/sec
Benchmark: prefix_selectivity/medium_selectivity/100
Time: 19.2 µs [19.122 µs 19.236 µs 19.364 µs]
Throughput: ~52,000 ops/sec
Benchmark: prefix_selectivity/low_selectivity/600
Time: 127.5 µs [126.65 µs 127.49 µs 128.37 µs]
Throughput: ~7,800 ops/sec
Benchmark: prefix_selectivity/empty_prefix/10000
Time: 1.68 ms [1.6651 ms 1.6756 ms 1.6863 ms]
Throughput: ~596 ops/sec
Key Observations:
Benchmark: dictionary_size/medium_selectivity/1000
Time: 16.6 µs [16.521 µs 16.620 µs 16.750 µs]
Benchmark: dictionary_size/medium_selectivity/10000
Time: 17.9 µs [17.683 µs 17.905 µs 18.115 µs]
Benchmark: dictionary_size/medium_selectivity/100000
Time: 18.8 µs [18.618 µs 18.762 µs 18.900 µs]
Key Observations:
Benchmark: backend_comparison/DoubleArrayTrie
Time: 18.3 µs [18.210 µs 18.308 µs 18.413 µs]
Benchmark: backend_comparison/DynamicDawg
Time: 18.1 µs [18.029 µs 18.099 µs 18.172 µs]
Relative: DynamicDawg is 0.99× (1.1% faster - within measurement error)
Key Observations:
Benchmark: tree_depth/depth/5
Time: 3.55 µs [3.5374 µs 3.5529 µs 3.5699 µs]
Benchmark: tree_depth/depth/10
Time: 6.08 µs [6.0305 µs 6.0779 µs 6.1308 µs]
Benchmark: tree_depth/depth/15
Time: 10.6 µs [10.514 µs 10.570 µs 10.627 µs]
Benchmark: tree_depth/depth/20
Time: 15.9 µs [15.840 µs 15.915 µs 15.990 µs]
Key Observations:
Benchmark: collection_overhead/count_only
Time: 19.1 µs [18.603 µs 19.118 µs 19.757 µs]
Benchmark: collection_overhead/collect_vec
Time: 19.6 µs [19.574 µs 19.640 µs 19.714 µs]
Benchmark: collection_overhead/collect_strings
Time: 21.5 µs [21.370 µs 21.457 µs 21.559 µs]
Key Observations:
Benchmark: prefix_navigation/nav_length/0
Time: 167 ns [164.25 ns 167.07 ns 170.63 ns]
Benchmark: prefix_navigation/nav_length/1
Time: 252 ns [251.18 ns 252.03 ns 252.91 ns]
Benchmark: prefix_navigation/nav_length/2
Time: 328 ns [326.57 ns 327.66 ns 328.88 ns]
Benchmark: prefix_navigation/nav_length/3
Time: 429 ns [426.11 ns 429.08 ns 432.90 ns]
Benchmark: prefix_navigation/nav_length/4
Time: 528 ns [525.69 ns 527.91 ns 530.24 ns]
Benchmark: prefix_navigation/nav_length/5
Time: 635 ns [632.70 ns 634.81 ns 637.21 ns]
Benchmark: prefix_navigation/nav_length/6
Time: 721 ns [716.03 ns 720.83 ns 727.30 ns]
Benchmark: prefix_navigation/nav_length/7
Time: 793 ns [790.91 ns 793.53 ns 796.29 ns]
Key Observations:
Benchmark: iteration_only/iterate_100_results
Time: 17.1 µs [17.014 µs 17.094 µs 17.184 µs]
Key Observations:
Flamegraph: docs/optimization/prefix_zipper_baseline_flamegraph.svg (5.8 MB)
Profile Command: cargo flamegraph --bench prefix_zipper_benchmarks -- --bench "prefix_selectivity/medium_selectivity"
Samples: 25,160 samples (65.5 billion cycles)
Top 10 Functions by Self Time:
PrefixIterator::next (3,416 samples)exp (math library - criterion overhead)rayon::bridge_producer_consumer (criterion parallel overhead)criterion::Bencher::iter (benchmark infrastructure)malloc (heap allocation)realloc (heap reallocation)DoubleArrayTrie::from_terms (setup overhead)RawVec::grow_one (Vec growth)cfree (deallocation)rayon::slice::sort (criterion sorting)Analysis:
src/dictionary/prefix_zipper.rs:254-279FilterMap::next on children iteratorDictZipper::children closure invocationArc::clone for shared dictionary dataVec::clone for path cloningVec::push for path extensionchild_path = path.clone())Vec::with_capacity_in → allocatorsrc/dictionary/double_array_trie_zipper.rs - DATShared clone on descend(To be analyzed using criterion's allocation tracking or dhat)
Estimated from code analysis (to be confirmed by profiling):
Path cloning (prefix_zipper.rs:256):
Stack growth (prefix_zipper.rs:258):
Children iteration (backend-specific):
For medium_selectivity benchmark (100 results):
Prioritize optimizations that address:
Based on profiling data (actual CPU time percentages):
| Rank | Target | % Impact | Complexity | Risk |
|---|---|---|---|---|
| 1 | Remove redundant path tracking | 2.19% (Vec::clone) + 1.88% (Vec::push/grow) = 4.07% | Medium | Medium - requires API change |
| 2 | Pre-allocate stack capacity | 2.37% (realloc) | Low | Very Low - internal change only |
| 3 | Batch children() calls | ~1-2% (estimated from FilterMap overhead) | Medium | Low - internal optimization |
Note: Optimizations target 4.07% + 2.37% = 6.44% direct improvement in measured profile, which translates to ~8-10% real throughput gain after accounting for benchmark overhead.
Based on baseline measurements and profiling data:
Hypothesis: Path cloning at line 256 accounts for >3% of execution time in iteration-heavy scenarios.
Supporting Evidence:
Profiling Validation:
<Vec as Clone>::clone visible in flamegraph at 2.19%Vec::push → RawVec::grow_one visible at 1.88%Optimization: Remove redundant path storage from stack (all zippers already store paths internally via path() method)
Expected Impact: 4.07% → ~5-6% throughput improvement
Hypothesis: Initial stack capacity of 1 causes multiple reallocations, adding >2% overhead.
Supporting Evidence:
realloc = 2.37% of total timevec![(prefix_zipper, prefix_path)] (capacity 1)Profiling Validation:
realloc visible at 2.37% self timeRawVec::grow_one visible at 2.00%Optimization: Pre-allocate stack with capacity 16-32 based on typical tree depth
Expected Impact: 2.37% → ~3% throughput improvement
Hypothesis: DynamicDawg's children() allocates Vec per call, causing >15% overhead vs DoubleArrayTrie.
Supporting Evidence:
Benchmark Results:
Analysis: Lock overhead and Vec allocation are NOT bottlenecks. Both backends dominated by iteration overhead, not backend-specific costs.
Conclusion: No optimization needed for backend comparison
docs/optimization/prefix_zipper_baseline_flamegraph.svg)src/dictionary/prefix_zipper.rsbenches/prefix_zipper_benchmarks.rsdocs/optimization/prefix_zipper_optimization_log.mdCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |