use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use rust_rocksdb::{DB, Options};
mod util;
use util::DBPath;
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
static COUNTING: AtomicBool = AtomicBool::new(false);
struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNTING.load(Ordering::Relaxed) {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if COUNTING.load(Ordering::Relaxed) {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
fn count_allocs(f: impl FnOnce()) -> usize {
let before = ALLOCS.load(Ordering::Relaxed);
COUNTING.store(true, Ordering::Relaxed);
f();
COUNTING.store(false, Ordering::Relaxed);
ALLOCS.load(Ordering::Relaxed) - before
}
#[test]
fn hot_read_paths_do_not_allocate() {
let path = DBPath::new("_rust_rocksdb_alloc_counts");
let mut opts = Options::default();
opts.create_if_missing(true);
let db = DB::open(&opts, &path).unwrap();
db.put(b"prefix_a/1", b"v").unwrap();
db.put(b"prefix_b/1", b"v").unwrap();
db.put(b"aaaa", b"v").unwrap();
db.put(b"b", b"v").unwrap();
db.put(b"k", b"value").unwrap();
for _ in 0..4 {
assert!(db.prefix_exists(b"prefix_a").unwrap());
}
let allocs = count_allocs(|| {
for _ in 0..64 {
assert!(db.prefix_exists(b"prefix_a").unwrap());
assert!(!db.prefix_exists(b"prefix_z").unwrap());
}
});
assert_eq!(
allocs, 0,
"prefix_exists should not allocate once its bound buffers are warm, got {allocs} \
allocations over 128 calls"
);
let probes: &[(&[u8], bool)] = &[
(b"aaaaa", false),
(b"aaaa", true),
(b"aaa", true),
(b"aa", true),
(b"a", true),
(b"b", true),
(b"c", false),
];
for _ in 0..4 {
for (prefix, expected) in probes {
assert_eq!(db.prefix_exists(prefix).unwrap(), *expected);
}
}
let allocs = count_allocs(|| {
for _ in 0..16 {
for (prefix, expected) in probes {
assert_eq!(db.prefix_exists(prefix).unwrap(), *expected);
}
}
});
assert_eq!(
allocs, 0,
"prefix probes of varying length should reuse the bound buffers, got {allocs}"
);
let mut buf = vec![0u8; 64];
let _ = db.get_into_buffer(b"k", &mut buf).unwrap();
let allocs = count_allocs(|| {
for _ in 0..64 {
let _ = db.get_into_buffer(b"k", &mut buf).unwrap();
}
});
assert_eq!(
allocs, 0,
"get_into_buffer should not allocate, got {allocs} over 64 calls"
);
}