Comprehensive profiling using flame graphs identified critical bottlenecks in the liblevenshtein-rust library. Profile-Guided Optimization (PGO) has been successfully set up to optimize hot paths based on real-world usage patterns.
Created benches/profiling_benchmark.rs to exercise realistic workloads:
Workload Characteristics:
/usr/share/dict/words or synthetic)contains() callsBenchmark Results:
Dictionary: 10,000 words
DAWG construction: 2.5ms
Transducer construction: 34ns
5,000 queries: 4.1 seconds (avg 823µs per query)
1M contains() calls: 165ms
Total query results: 1,084,000 matches found
Generated two flame graphs:
flamegraph.svg - Initial profiling (queries returned 0 results)flamegraph_improved.svg - Improved with realistic queries (1.08M results)The #1 Bottleneck: Arc atomic operations dominate execution
Arc::clone (increment): ~20.77%
core::sync::atomic::AtomicUsize::fetch_add: 17.18% in transition(), 3.59% in root()Arc::drop (decrement): ~20.65%
core::sync::atomic::AtomicUsize::fetch_sub: 116M atomic decrement samplesRoot Cause:
// Current implementation in dawg.rs:282-306
fn transition(&self, label: u8) -> Option<Self> {
// ...
Some(DawgDictionaryNode {
nodes: Arc::clone(&self.nodes), // <-- Atomic increment here!
node_idx: *idx,
})
}
// When dropped:
impl Drop for Arc {
fn drop(&mut self) {
// Atomic decrement here!
}
}
Impact:
Hot Path: Adaptive edge lookup working as designed
core::slice::<impl [T]>::binary_search_by_key: 152M samples (26.88%)<core::cmp::Ordering as core::cmp::PartialEq>::eq: 5.85%core::hint::select_unpredictable: 7.60%core::slice::<impl [T]>::get_unchecked: 7.54%Analysis: This is expected and optimal - binary search for nodes with ≥8 edges. Previous benchmarks showed:
alloc::vec::Vec<T,A>::len: 60M samples (10.66%)liblevenshtein::dictionary::Dictionary::contains: 531M samplesHot Path Breakdown:
Critical Finding:
Arc reference counting overhead exceeds all other operations combined, including the binary search optimization we just implemented. This represents the next major optimization opportunity.
Created automated PGO build script: pgo_build.sh
PGO Workflow:
#!/bin/bash
# 1. Clean previous data
rm -rf /tmp/pgo-data && mkdir -p /tmp/pgo-data
# 2. Build with instrumentation
RUSTFLAGS="-C target-cpu=native -C profile-generate=/tmp/pgo-data" \
cargo build --release --bench profiling_benchmark
# 3. Run profiling workload
./target/release/deps/profiling_benchmark-*
# 4. Merge profiling data
llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data/*.profraw
# 5. Build with PGO
RUSTFLAGS="-C target-cpu=native -C profile-use=/tmp/pgo-data/merged.profdata" \
cargo build --release
Successful PGO build completed:
Step 1: Instrumented build: 11.62s
Step 2: Profiling run: 5.52s (queries) + 0.20s (contains)
Step 3: Data merge: ~1s
Step 4: PGO-optimized build: 14.76s
Total PGO workflow: ~32 seconds
PGO Optimizations Applied:
PGO-optimized binary: target/release/liblevenshtein-cli (11MB)
The compiler now has profiling data showing:
Problem: 41% of execution time spent on Arc atomic operations
Solutions:
Replace Arc with Rc for single-threaded use
DawgDictionaryNode variant with RcUse raw pointers with manual lifetime management
*const Vec<DawgNode> with lifetime parameterCopy-on-write with bump allocator
Cache DawgDictionaryNode between operations
Expected Impact: 15-30% overall performance improvement
Based on profiling, lower-priority optimizations:
Inline Vector Length Checks (10.66% overhead)
#[inline(always)] on hot pathsSIMD Edge Lookup (potential 5-10% improvement)
Memory Pool for State Objects
benches/profiling_benchmark.rs - Realistic profiling workloadflamegraph.svg - Initial flame graphflamegraph_improved.svg - Improved flame graph with realistic queriespgo_build.sh - Automated PGO build scriptpgo_build_log.txt - PGO build logdocs/PROFILING_AND_PGO_RESULTS.md - This documentRUSTFLAGS="-C target-cpu=native" cargo flamegraph --bench profiling_benchmark --output flamegraph.svg
./pgo_build.sh
# After PGO build:
RUSTFLAGS="-C target-cpu=native" cargo bench
Can 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 |