Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "quickleaf"
version = "0.4.4"
version = "0.4.5"
edition = "2021"
license = "Apache-2.0"
authors = ["Philippe Assis <codephilippe@gmail.com>"]
Expand Down
145 changes: 41 additions & 104 deletions README.md

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions examples/cache_performance_analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::time::Instant;
use quickleaf::Cache;

fn main() {
println!("Testing real-world cache performance scenarios...");

// Test 1: Random access patterns (where prefetch should hurt)
test_random_access();

// Test 2: Sequential access patterns (where prefetch should help)
test_sequential_access();

// Test 3: Large cache list operations
test_large_list_operations();

// Test 4: Cleanup operations with many expired items
test_cleanup_operations();
}

fn test_random_access() {
println!("\n--- Random Access Test ---");
let mut cache = Cache::new(10000);

// Pre-populate with 10k items
for i in 0..10000 {
cache.insert(format!("item{:05}", i), format!("value{}", i));
}

// Random access pattern
let mut rng_seed = 42u64;
let iterations = 100000;

let start = Instant::now();
for _ in 0..iterations {
// Simple LCG for reproducible "random" numbers
rng_seed = rng_seed.wrapping_mul(1664525).wrapping_add(1013904223);
let index = (rng_seed % 10000) as usize;
let key = format!("item{:05}", index);
std::hint::black_box(cache.get(&key));
}
let duration = start.elapsed();

println!("Random access ({} ops): {:?}", iterations, duration);
println!("Average per access: {:?}", duration / iterations);
}

fn test_sequential_access() {
println!("\n--- Sequential Access Test ---");
let mut cache = Cache::new(10000);

// Pre-populate
for i in 0..10000 {
cache.insert(format!("seq{:05}", i), format!("value{}", i));
}

let iterations = 10;

let start = Instant::now();
for _ in 0..iterations {
// Sequential access through the entire cache
for i in 0..10000 {
let key = format!("seq{:05}", i);
std::hint::black_box(cache.get(&key));
}
}
let duration = start.elapsed();

println!("Sequential access ({} full sweeps): {:?}", iterations, duration);
println!("Average per access: {:?}", duration / (iterations * 10000));
}

fn test_large_list_operations() {
println!("\n--- Large List Operations Test ---");
let mut cache = Cache::new(50000);

// Pre-populate with 50k items
for i in 0..50000 {
cache.insert(format!("list{:06}", i), i);
}

let iterations = 100;

let start = Instant::now();
for _ in 0..iterations {
let mut props = quickleaf::ListProps::default();
props.limit = 1000; // Get 1000 items each time
std::hint::black_box(cache.list(props).unwrap());
}
let duration = start.elapsed();

println!("List operations ({} iterations, 1000 items each): {:?}", iterations, duration);
println!("Average per list operation: {:?}", duration / iterations);
}

fn test_cleanup_operations() {
println!("\n--- Cleanup Operations Test ---");
let mut cache = Cache::new(20000);

// Pre-populate with mix of expired and valid items
for i in 0..10000 {
// Add expired items (very short TTL)
cache.insert_with_ttl(
format!("expired{:05}", i),
format!("value{}", i),
std::time::Duration::from_nanos(1)
);
}

// Add some valid items
for i in 10000..20000 {
cache.insert(format!("valid{:05}", i), format!("value{}", i));
}

// Wait a bit to ensure expiration
std::thread::sleep(std::time::Duration::from_millis(1));

let iterations = 1000;

let start = Instant::now();
for _ in 0..iterations {
std::hint::black_box(cache.cleanup_expired());
}
let duration = start.elapsed();

println!("Cleanup operations ({} iterations): {:?}", iterations, duration);
println!("Average per cleanup: {:?}", duration / iterations);
}
15 changes: 0 additions & 15 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::error::Error;
use crate::event::Event;
use crate::filters::apply_filter_fast;
use crate::list_props::{ListProps, Order, StartAfter};
use crate::prefetch::{Prefetch, PrefetchExt};
use indexmap::IndexMap;
use std::fmt::Debug;
use std::sync::mpsc::Sender;
Expand Down Expand Up @@ -790,10 +789,6 @@ impl Cache {
/// ```
#[inline]
pub fn get(&mut self, key: &str) -> Option<&Value> {
if let Some((_, item)) = self.map.get_key_value(key) {
item.prefetch_read();
}

let is_expired = match self.map.get(key) {
Some(item) => {
if let Some(ttl) = item.ttl_millis {
Expand Down Expand Up @@ -935,8 +930,6 @@ impl Cache {
let mut expired_keys = Vec::with_capacity(self.map.len() / 4);

for (key, item) in &self.map {
item.prefetch_read();

if let Some(ttl) = item.ttl_millis {
if (current_time - item.created_at) > ttl {
expired_keys.push(key.clone());
Expand All @@ -946,10 +939,6 @@ impl Cache {

let removed_count = expired_keys.len();

if !expired_keys.is_empty() {
Prefetch::sequential_read_hints(expired_keys.as_ptr(), expired_keys.len());
}

for key in expired_keys {
if let Some(item) = self.map.swap_remove(&key) {
self.send_remove(key, item.value);
Expand Down Expand Up @@ -1008,10 +997,6 @@ impl Cache {
let mut keys: Vec<String> = self.map.keys().cloned().collect();
keys.sort();

if !keys.is_empty() {
Prefetch::sequential_read_hints(keys.as_ptr(), keys.len());
}

match props.order {
Order::Asc => self.resolve_order(keys.iter(), props),
Order::Desc => self.resolve_order(keys.iter().rev(), props),
Expand Down
Loading