From 3db25cf84c0531868425ff961693c5134de73a6a Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 09:34:05 +0300 Subject: [PATCH 1/6] perf/btree: add method for getting raw offset of cell payload start --- core/storage/btree.rs | 4 ++-- core/storage/sqlite3_ondisk.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/storage/btree.rs b/core/storage/btree.rs index adc0d5334..5a23b6706 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -2559,8 +2559,8 @@ impl BTreeCursor { // Hence: when we enter this branch with overflow_cells.len() == 1, we know that left-shifting has happened and we need to subtract 1. let actual_cell_idx = first_cell_divider + sibling_pointer - parent_contents.overflow_cells.len(); - let (start_of_cell, _) = - parent_contents.cell_get_raw_region(actual_cell_idx, usable_space); + let start_of_cell = + parent_contents.cell_get_raw_start_offset(actual_cell_idx); let buf = parent_contents.as_ptr().as_mut_ptr(); unsafe { buf.add(start_of_cell) } }; diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 4d900eee9..9c6698a03 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -660,6 +660,13 @@ impl PageContent { self.offset + self.header_size() } + /// Get the start offset of a cell's payload, not taking into account the 100-byte offset that is present on page 1. + pub fn cell_get_raw_start_offset(&self, idx: usize) -> usize { + let cell_pointer_array_start = self.cell_pointer_array_offset(); + let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES); + self.read_u16_no_offset(cell_pointer) as usize + } + /// Get region(start end length) of a cell's payload pub fn cell_get_raw_region(&self, idx: usize, usable_size: usize) -> (usize, usize) { let buf = self.as_ptr(); From c98136c8c4d7a1b0e90d6ef3d9ed1d28743e862d Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 09:37:33 +0300 Subject: [PATCH 2/6] btree: use new cell start helper method in cell_get_raw_region --- core/storage/sqlite3_ondisk.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 9c6698a03..2191cc81a 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -671,18 +671,15 @@ impl PageContent { pub fn cell_get_raw_region(&self, idx: usize, usable_size: usize) -> (usize, usize) { let buf = self.as_ptr(); let ncells = self.cell_count(); - let (cell_pointer_array_start, _) = self.cell_pointer_array_offset_and_size(); assert!(idx < ncells, "cell_get: idx out of bounds"); - let cell_pointer = cell_pointer_array_start + (idx * CELL_PTR_SIZE_BYTES); - let cell_pointer = self.read_u16_no_offset(cell_pointer) as usize; - let start = cell_pointer; + let start = self.cell_get_raw_start_offset(idx); let payload_overflow_threshold_max = payload_overflow_threshold_max(self.page_type(), usable_size); let payload_overflow_threshold_min = payload_overflow_threshold_min(self.page_type(), usable_size); let len = match self.page_type() { PageType::IndexInterior => { - let (len_payload, n_payload) = read_varint(&buf[cell_pointer + 4..]).unwrap(); + let (len_payload, n_payload) = read_varint(&buf[start + 4..]).unwrap(); let (overflows, to_read) = payload_overflows( len_payload as usize, payload_overflow_threshold_max, @@ -696,11 +693,11 @@ impl PageContent { } } PageType::TableInterior => { - let (_, n_rowid) = read_varint(&buf[cell_pointer + 4..]).unwrap(); + let (_, n_rowid) = read_varint(&buf[start + 4..]).unwrap(); 4 + n_rowid } PageType::IndexLeaf => { - let (len_payload, n_payload) = read_varint(&buf[cell_pointer..]).unwrap(); + let (len_payload, n_payload) = read_varint(&buf[start..]).unwrap(); let (overflows, to_read) = payload_overflows( len_payload as usize, payload_overflow_threshold_max, @@ -718,8 +715,8 @@ impl PageContent { } } PageType::TableLeaf => { - let (len_payload, n_payload) = read_varint(&buf[cell_pointer..]).unwrap(); - let (_, n_rowid) = read_varint(&buf[cell_pointer + n_payload..]).unwrap(); + let (len_payload, n_payload) = read_varint(&buf[start..]).unwrap(); + let (_, n_rowid) = read_varint(&buf[start + n_payload..]).unwrap(); let (overflows, to_read) = payload_overflows( len_payload as usize, payload_overflow_threshold_max, From 4b27cc0d4652abfa01c75410a144dcc3c486a34b Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 09:54:33 +0300 Subject: [PATCH 3/6] btree: add fast path version of cell_get_raw_region --- core/storage/btree.rs | 17 ++++++++-- core/storage/sqlite3_ondisk.rs | 60 ++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 5a23b6706..65e9c8b49 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -2795,10 +2795,21 @@ impl BTreeCursor { { let old_page = old_page.as_ref().unwrap().get(); let old_page_contents = old_page.get_contents(); + let page_type = old_page_contents.page_type(); + let max_local = payload_overflow_threshold_max(page_type, usable_space); + let min_local = payload_overflow_threshold_min(page_type, usable_space); + let cell_count = old_page_contents.cell_count(); debug_validate_cells!(&old_page_contents, usable_space as u16); - for cell_idx in 0..old_page_contents.cell_count() { - let (cell_start, cell_len) = - old_page_contents.cell_get_raw_region(cell_idx, usable_space); + for cell_idx in 0..cell_count { + let (cell_start, cell_len) = old_page_contents + ._cell_get_raw_region_faster( + cell_idx, + usable_space, + cell_count, + max_local, + min_local, + page_type, + ); let buf = old_page_contents.as_ptr(); let cell_buf = &mut buf[cell_start..cell_start + cell_len]; // TODO(pere): make this reference and not copy diff --git a/core/storage/sqlite3_ondisk.rs b/core/storage/sqlite3_ondisk.rs index 2191cc81a..8d5c5eb1f 100644 --- a/core/storage/sqlite3_ondisk.rs +++ b/core/storage/sqlite3_ondisk.rs @@ -668,24 +668,42 @@ impl PageContent { } /// Get region(start end length) of a cell's payload + /// FIXME: make all usages of [cell_get_raw_region] to use the _faster version in cases where the method is called + /// repeatedly, since page_type, max_local, min_local are the same for all cells on the page. Also consider whether + /// max_local and min_local should be static properties of the page. pub fn cell_get_raw_region(&self, idx: usize, usable_size: usize) -> (usize, usize) { + let page_type = self.page_type(); + let max_local = payload_overflow_threshold_max(page_type, usable_size); + let min_local = payload_overflow_threshold_min(page_type, usable_size); + let cell_count = self.cell_count(); + self._cell_get_raw_region_faster( + idx, + usable_size, + cell_count, + max_local, + min_local, + page_type, + ) + } + + /// Get region(start end length) of a cell's payload + pub fn _cell_get_raw_region_faster( + &self, + idx: usize, + usable_size: usize, + cell_count: usize, + max_local: usize, + min_local: usize, + page_type: PageType, + ) -> (usize, usize) { let buf = self.as_ptr(); - let ncells = self.cell_count(); - assert!(idx < ncells, "cell_get: idx out of bounds"); + assert!(idx < cell_count, "cell_get: idx out of bounds"); let start = self.cell_get_raw_start_offset(idx); - let payload_overflow_threshold_max = - payload_overflow_threshold_max(self.page_type(), usable_size); - let payload_overflow_threshold_min = - payload_overflow_threshold_min(self.page_type(), usable_size); - let len = match self.page_type() { + let len = match page_type { PageType::IndexInterior => { let (len_payload, n_payload) = read_varint(&buf[start + 4..]).unwrap(); - let (overflows, to_read) = payload_overflows( - len_payload as usize, - payload_overflow_threshold_max, - payload_overflow_threshold_min, - usable_size, - ); + let (overflows, to_read) = + payload_overflows(len_payload as usize, max_local, min_local, usable_size); if overflows { 4 + to_read + n_payload } else { @@ -698,12 +716,8 @@ impl PageContent { } PageType::IndexLeaf => { let (len_payload, n_payload) = read_varint(&buf[start..]).unwrap(); - let (overflows, to_read) = payload_overflows( - len_payload as usize, - payload_overflow_threshold_max, - payload_overflow_threshold_min, - usable_size, - ); + let (overflows, to_read) = + payload_overflows(len_payload as usize, max_local, min_local, usable_size); if overflows { to_read + n_payload } else { @@ -717,12 +731,8 @@ impl PageContent { PageType::TableLeaf => { let (len_payload, n_payload) = read_varint(&buf[start..]).unwrap(); let (_, n_rowid) = read_varint(&buf[start + n_payload..]).unwrap(); - let (overflows, to_read) = payload_overflows( - len_payload as usize, - payload_overflow_threshold_max, - payload_overflow_threshold_min, - usable_size, - ); + let (overflows, to_read) = + payload_overflows(len_payload as usize, max_local, min_local, usable_size); if overflows { to_read + n_payload + n_rowid } else { From 3431add0e2aeb4fbe91416e0b9ba232873d72b2a Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 10:06:38 +0300 Subject: [PATCH 4/6] gitignore: add profiler json file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 77396357e..30bf4488b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ testing/*.log limbostress.log simulator.log **/*.txt +profile.json.gz From 2ed41bbb35b9f2c0954ec71abb47530bee2a72de Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 10:09:35 +0300 Subject: [PATCH 5/6] btree/insert: avoid calling self.usable_space() in a loop --- core/storage/btree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/storage/btree.rs b/core/storage/btree.rs index 65e9c8b49..c1619123e 100644 --- a/core/storage/btree.rs +++ b/core/storage/btree.rs @@ -2136,8 +2136,8 @@ impl BTreeCursor { if let CursorState::None = &self.state { self.state = CursorState::Write(WriteState::Start); } + let usable_space = self.usable_space(); let ret = loop { - let usable_space = self.usable_space(); let CursorState::Write(write_state) = &mut self.state else { panic!("expected write state"); }; From c5bdbe306dd82de2735234304ec75a486a28538c Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Thu, 7 Aug 2025 10:27:11 +0300 Subject: [PATCH 6/6] perf/wal: avoid accessing pages_in_frames unless necessary --- core/storage/wal.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/storage/wal.rs b/core/storage/wal.rs index 84f66196d..86e6a02a2 100644 --- a/core/storage/wal.rs +++ b/core/storage/wal.rs @@ -1279,11 +1279,12 @@ impl WalFile { let shared = self.get_shared(); { let mut frame_cache = shared.frame_cache.lock(); - let frames = frame_cache.get_mut(&page_id); - match frames.filter(|frames| !frames.is_empty()) { + match frame_cache.get_mut(&page_id) { Some(frames) => { - let pages_in_frames = shared.pages_in_frames.lock(); - turso_assert!(pages_in_frames.contains(&page_id), "page_id={page_id} must be in pages_in_frames if it's also already in frame_cache: pages_in_frames={:?}, frames={:?}", *pages_in_frames, frames); + if frames.is_empty() { + let mut pages_in_frames = shared.pages_in_frames.lock(); + pages_in_frames.push(page_id); + } frames.push(frame_id); } None => {