Currently we deserialize the entire record to compare them or to get a
particular column. This PR introduces efficient record operations such
as incremental column deserialization and efficient record comparison.
### Incremental Column Deserialization
- Introduced `RecordCursor` to keep track of how much of the header and
the record we have already parsed. Each `BTreeCursor` will have its own
`RecordCursor` similar to an `ImmutableRecord`.
- The `RecordCursor` gets the number of columns from schema when the
BTreeCursor is initialized in VDBE. This helps in cutting down heap
allocs by reserving the correct amount of space for underlying `Vec`s.
- `Immutable` record only carries the serialized `payload` now.
- We parse the header up till we reach the required serial type (denoted
by the column index) and then calculate the offsets and deserialize only
that particular slice of the payload.
- Manually inlined most of the deserialization code into `fn op_column`
code because the compiler is refusing to inline even with
`#[inline(always)]` hint. This is probably due to complicated control
flow.
- Tried to follow SQLite semantics, where it returns `Null` when the
requested column falls outside the number of columns available in the
record or when the payload is empty etc.
### Efficient Record Comparison ops
- Three record comparison function are introduced for Integer, String
and for general case which replaces the `compare_immutable`. These
functions compare a serialized record with deserialized one.
- `compare_records_int`: is used when the first field is integer, header
≤63 bytes, ≤13 total fields. No varint parsing, direct integer
extraction.
- `compare_records_string`: is used when the first field is text with
binary collation, header ≤63 bytes.
- `compare_records_generic`: is used in complex cases, custom
collations, large headers. Here we parse the record incrementally field
by field and comparing each field with the one from the deserialized
record. We early exit on the first mismatch saving on the
deserialization cost.
- `find_compare`: selects the optimal comparison strategy for a given
case and dispatches the function required.
### Benchmarks `main` vs `incremental_column`
I've used the `testing/testing.db` for this benchmark.
| Query | Main
| Incremental | % Change (Faster is +ve) |
|-------------------------------------------------------------|---------
-|-------------|------------------------|
| SELECT first_name FROM users | 1.3579ms
| 1.1452ms | 15.66 |
| SELECT age FROM users | 912.33µs
| 897.97µs | 1.57 |
| SELECT email FROM users | 1.3632ms
| 1.215ms | 10.87 |
| SELECT id FROM users | 1.4985ms
| 1.1762ms | 21.50 |
| SELECT first_name, last_name FROM users | 1.5736ms
| 1.4616ms | 7.11 |
| SELECT first_name, last_name, email FROM users | 1.7965ms
| 1.754ms | 2.36 |
| SELECT id, first_name, last_name, email, age FROM users | 2.3545ms
| 2.4059ms | -2.18 |
| SELECT * FROM users | 3.5731ms
| 3.7587ms | -5.19 |
| SELECT * FROM users WHERE age = 30 | 87.947µs
| 85.545µs | 2.73 |
| SELECT id, first_name FROM users WHERE first_name LIKE 'John%' |
1.8594ms | 1.6781ms | 9.75 |
| SELECT age FROM users LIMIT 1000 | 100.27µs
| 95.418µs | 4.83 |
| SELECT first_name, age, email FROM users LIMIT 1000 | 176.04µs
| 167.56µs | 4.81 |
Closes: https://github.com/tursodatabase/turso/issues/1703Closes#1923
This is done because the compiler is refusing to inline even after
adding inline hint.
- Get refvalues from directly from registers without using
`make_record`
compare_records_int
compare_records_string
compare_records_generic
comapre_records_generic will still be more efficient than compare-
_immutable because it deserializes the record column by column
I am currently working on upgrading Rust to the latest version
(https://github.com/tursodatabase/limbo/pull/1807). Initially, I wanted
to address all Clippy errors in that PR, but I decided to move them into
a separate one to make it easier to review.
**Note:** In the latest version of Clippy, it is considered an error to
place variables after a comma in string formatting macros (e.g.,
`format!`), rather than inside the `{}` placeholders. That's where most
of the errors are coming from.
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#1827
Although concurrency doesn't work yet (#2059), we do want to support
concurrency, and one good first step is not to make the simulator hang
forever once any connection gets a `Busy` result from the VDBE
Closes#2060
Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>
Closes#2061
- Also added a benchmark for opening databases, the main thing that is
slowing `Database::open_file` is `parse_schema_rows`.
- `to_uppercase` was being called multiple times, leaving a relevant
mark on stack traces due to multiple allocations. `make_ascii_upper`
reuses the memory and is faster due to not handling unicode characters
(still compatible with sqlite).
- Do direct btree calls instead of creating a program for updating
`Schema` with `Schema::make_from_btree`.
- Faster type substr comparison using fixed size `u8` slices.
<img width="952" height="507" alt="image" src="https://github.com/user-
attachments/assets/0d0c52ff-05a1-431e-a93d-e333b53c0bb8" />
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#2042