mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-31 05:44:25 +01:00
Merge 'Cache reserved_space and page_size values at Pager init to prevent doing redundant IO' from Krishna Vishal
### Problem Profiling revealed that `usable_space()` calls were consuming 60% of total execution time for simple SELECT queries, making Limbo approximately `6x` slower than SQLite for SELECT operations. The bottleneck was caused by `usable_space()` performing expensive I/O operations on every call to read `page_size` and `reserved_space` from the database header, despite `page_size` values being effectively immutable after database initialization. Only `reserved_space` is allowed to increase in SQLite. Evidence: https://share.firefox.dev/44tCUIy ### Solution Implemented OnceCell-based caching for both page_size and reserved_space values in the Pager struct: `page_size: OnceCell<u16>` - Page size is immutable after database initialization per SQLite specification `reserved_space: OnceCell<u8>` - Reserved space rarely changes and only grows, safe to cache ### Performance Impact Benchmark results: Simple SELECT query time reduced from ~2.89ms to ~1.29ms (~55% improvement) Closes #1852
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::types::CursorResult;
|
||||
use crate::{Buffer, LimboError, Result};
|
||||
use crate::{Completion, WalFile};
|
||||
use parking_lot::RwLock;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::cell::{OnceCell, RefCell, UnsafeCell};
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
@@ -222,6 +222,11 @@ pub struct Pager {
|
||||
/// Mutex for synchronizing database initialization to prevent race conditions
|
||||
init_lock: Arc<Mutex<()>>,
|
||||
allocate_page1_state: RefCell<AllocatePage1State>,
|
||||
/// Cache page_size and reserved_space at Pager init and reuse for subsequent
|
||||
/// `usable_space` calls. TODO: Invalidate reserved_space when we add the functionality
|
||||
/// to change it.
|
||||
page_size: OnceCell<u16>,
|
||||
reserved_space: OnceCell<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -286,6 +291,8 @@ impl Pager {
|
||||
is_empty,
|
||||
init_lock,
|
||||
allocate_page1_state,
|
||||
page_size: OnceCell::new(),
|
||||
reserved_space: OnceCell::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -570,9 +577,15 @@ impl Pager {
|
||||
/// The usable size of a page might be an odd number. However, the usable size is not allowed to be less than 480.
|
||||
/// In other words, if the page size is 512, then the reserved space size cannot exceed 32.
|
||||
pub fn usable_space(&self) -> usize {
|
||||
let page_size = header_accessor::get_page_size(self).unwrap_or_default() as u32;
|
||||
let reserved_space = header_accessor::get_reserved_space(self).unwrap_or_default() as u32;
|
||||
(page_size - reserved_space) as usize
|
||||
let page_size = *self
|
||||
.page_size
|
||||
.get_or_init(|| header_accessor::get_page_size(self).unwrap_or_default());
|
||||
|
||||
let reserved_space = *self
|
||||
.reserved_space
|
||||
.get_or_init(|| header_accessor::get_reserved_space(self).unwrap_or_default());
|
||||
|
||||
(page_size as usize) - (reserved_space as usize)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
Reference in New Issue
Block a user