mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-20 15:35:29 +01:00
Merge 'perf: a few small insert optimizations' from Jussi Saurio
1. We spend a lot of time in `cell_get_raw_region` in the balancing
routine, and especially calling `contents.page_type()` there a lot, so
extract a version that can take some precomputed arguments so those
don't have to be redundantly computed multiple times for successive
calls where those values are going to be the same
2. Avoid calling `self.usable_space()` in a loop in
`insert_into_page()`.
3. Avoid accessing `pages_in_frames` lock if we're not going to modify
it
main improvement is to the "insert 100 rows" bench which ends up doing
balancing a lot:
```
Insert rows in batches/limbo_insert_1_rows
time: [22.856 µs 24.342 µs 27.496 µs]
change: [-3.3579% +15.495% +67.671%] (p = 0.62 > 0.05)
No change in performance detected.
Benchmarking Insert rows in batches/limbo_insert_10_rows: Collecting 100 samples in estim
Insert rows in batches/limbo_insert_10_rows
time: [32.196 µs 32.604 µs 32.981 µs]
change: [+1.3253% +2.9177% +4.5863%] (p = 0.00 < 0.05)
Performance has regressed.
Insert rows in batches/limbo_insert_100_rows
time: [89.425 µs 92.105 µs 96.304 µs]
change: [-18.317% -13.605% -9.1022%] (p = 0.00 < 0.05)
Performance has improved.
```
Reviewed-by: Preston Thorpe <preston@turso.tech>
Closes #2483
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,3 +38,4 @@ testing/*.log
|
||||
limbostress.log
|
||||
simulator.log
|
||||
**/*.txt
|
||||
profile.json.gz
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
@@ -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) }
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -720,28 +720,50 @@ impl PageContent {
|
||||
self.offset + self.header_size()
|
||||
}
|
||||
|
||||
/// 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();
|
||||
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");
|
||||
/// 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);
|
||||
let cell_pointer = self.read_u16_no_offset(cell_pointer) as usize;
|
||||
let start = cell_pointer;
|
||||
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() {
|
||||
self.read_u16_no_offset(cell_pointer) as usize
|
||||
}
|
||||
|
||||
/// 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();
|
||||
assert!(idx < cell_count, "cell_get: idx out of bounds");
|
||||
let start = self.cell_get_raw_start_offset(idx);
|
||||
let len = match page_type {
|
||||
PageType::IndexInterior => {
|
||||
let (len_payload, n_payload) = read_varint(&buf[cell_pointer + 4..]).unwrap();
|
||||
let (overflows, to_read) = payload_overflows(
|
||||
len_payload as usize,
|
||||
payload_overflow_threshold_max,
|
||||
payload_overflow_threshold_min,
|
||||
usable_size,
|
||||
);
|
||||
let (len_payload, n_payload) = read_varint(&buf[start + 4..]).unwrap();
|
||||
let (overflows, to_read) =
|
||||
payload_overflows(len_payload as usize, max_local, min_local, usable_size);
|
||||
if overflows {
|
||||
4 + to_read + n_payload
|
||||
} else {
|
||||
@@ -749,17 +771,13 @@ 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 (overflows, to_read) = payload_overflows(
|
||||
len_payload as usize,
|
||||
payload_overflow_threshold_max,
|
||||
payload_overflow_threshold_min,
|
||||
usable_size,
|
||||
);
|
||||
let (len_payload, n_payload) = read_varint(&buf[start..]).unwrap();
|
||||
let (overflows, to_read) =
|
||||
payload_overflows(len_payload as usize, max_local, min_local, usable_size);
|
||||
if overflows {
|
||||
to_read + n_payload
|
||||
} else {
|
||||
@@ -771,14 +789,10 @@ 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 (overflows, to_read) = payload_overflows(
|
||||
len_payload as usize,
|
||||
payload_overflow_threshold_max,
|
||||
payload_overflow_threshold_min,
|
||||
usable_size,
|
||||
);
|
||||
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, max_local, min_local, usable_size);
|
||||
if overflows {
|
||||
to_read + n_payload + n_rowid
|
||||
} else {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user