Merge 'perf/prepare: box many less frequently used AST nodes' from Jussi Saurio

`sqlite3-parser` spends a lot of time in `yy_reduce` assigning different
`enum YYMINORTYPE` members, and it suffers from bad performance because
the stack size of the enum is very large, and the size of individual
members varies wildly. This PR reduces the `YYMINORTYPE` stack size from
496 to 264 bytes and has the following effect:
Preparing statement:
```sql
Prepare `SELECT 1`/Limbo/SELECT 1
                        time:   [620.37 ns 621.08 ns 621.79 ns]
                        change: [-17.811% -17.582% -17.380%] (p = 0.00 < 0.05)
                        Performance has improved.

Prepare `SELECT * FROM users LIMIT 1`/Limbo/SELECT * FROM users LIMIT 1
                        time:   [1.2215 µs 1.2231 µs 1.2248 µs]
                        change: [-12.926% -12.627% -12.272%] (p = 0.00 < 0.05)
                        Performance has improved.

Benchmarking Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou...: Collecting 100 samples in estimated 5.0152 s (
Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou...
                        time:   [3.1056 µs 3.1096 µs 3.1138 µs]
                        change: [-13.279% -12.995% -12.712%] (p = 0.00 < 0.05)
                        Performance has improved.

```
Execute (mainly to check for regressions):
```sql
Execute `SELECT * FROM users LIMIT ?`/Limbo/1
                        time:   [402.19 ns 402.75 ns 403.36 ns]
                        change: [-3.4845% -2.4003% -1.6539%] (p = 0.00 < 0.05)
                        Performance has improved.

Execute `SELECT * FROM users LIMIT ?`/Limbo/10
                        time:   [2.7920 µs 2.7977 µs 2.8036 µs]
                        change: [-1.3135% -1.0123% -0.7132%] (p = 0.00 < 0.05)
                        Change within noise threshold.

Execute `SELECT * FROM users LIMIT ?`/Limbo/50
                        time:   [13.577 µs 13.633 µs 13.690 µs]
                        change: [-1.7709% -1.3575% -0.9563%] (p = 0.00 < 0.05)
                        Change within noise threshold.
```

Closes #936
This commit is contained in:
Pekka Enberg
2025-02-09 08:45:42 +02:00
5 changed files with 48 additions and 43 deletions

View File

@@ -13,7 +13,7 @@ pub fn translate_delete(
query_mode: QueryMode,
schema: &Schema,
tbl_name: &QualifiedName,
where_clause: Option<Expr>,
where_clause: Option<Box<Expr>>,
limit: Option<Box<Limit>>,
syms: &SymbolTable,
) -> Result<ProgramBuilder> {
@@ -35,7 +35,7 @@ pub fn translate_delete(
pub fn prepare_delete_plan(
schema: &Schema,
tbl_name: &QualifiedName,
where_clause: Option<Expr>,
where_clause: Option<Box<Expr>>,
limit: Option<Box<Limit>>,
) -> Result<Plan> {
let table = match schema.get_table(tbl_name.name.0.as_str()) {
@@ -53,7 +53,12 @@ pub fn prepare_delete_plan(
let mut where_predicates = vec![];
// Parse the WHERE clause
parse_where(where_clause, &table_references, None, &mut where_predicates)?;
parse_where(
where_clause.map(|e| *e),
&table_references,
None,
&mut where_predicates,
)?;
// Parse the LIMIT/OFFSET clause
let (resolved_limit, resolved_offset) = limit.map_or(Ok((None, None)), |l| parse_limit(*l))?;

View File

@@ -305,7 +305,7 @@ pub fn prepare_select_plan(
exprs: group_by.exprs,
having: if let Some(having) = group_by.having {
let mut predicates = vec![];
break_predicate_at_and_boundaries(having, &mut predicates);
break_predicate_at_and_boundaries(*having, &mut predicates);
for expr in predicates.iter_mut() {
bind_column_references(
expr,

View File

@@ -160,19 +160,19 @@ impl Stmt {
} => Err(custom_err!("ORDER BY without LIMIT on DELETE")),
Self::Insert {
columns: Some(columns),
body: InsertBody::Select(select, ..),
body,
..
} => match select.body.select.column_count() {
ColumnCount::Fixed(n) if n != columns.len() => {
Err(custom_err!("{} values for {} columns", n, columns.len()))
} => match &**body {
InsertBody::Select(select, ..) => match select.body.select.column_count() {
ColumnCount::Fixed(n) if n != columns.len() => {
Err(custom_err!("{} values for {} columns", n, columns.len()))
}
_ => Ok(()),
},
InsertBody::DefaultValues => {
Err(custom_err!("0 values for {} columns", columns.len()))
}
_ => Ok(()),
},
Self::Insert {
columns: Some(columns),
body: InsertBody::DefaultValues,
..
} => Err(custom_err!("0 values for {} columns", columns.len())),
Self::Update {
order_by: Some(_),
limit: None,

View File

@@ -78,11 +78,11 @@ pub enum Stmt {
Attach {
/// filename
// TODO distinction between ATTACH and ATTACH DATABASE
expr: Expr,
expr: Box<Expr>,
/// schema name
db_name: Expr,
db_name: Box<Expr>,
/// password
key: Option<Expr>,
key: Option<Box<Expr>>,
},
/// `BEGIN`: tx type, tx name
Begin(Option<TransactionType>, Option<Name>),
@@ -125,13 +125,13 @@ pub enum Stmt {
/// `BEFORE`/`AFTER`/`INSTEAD OF`
time: Option<TriggerTime>,
/// `DELETE`/`INSERT`/`UPDATE`
event: TriggerEvent,
event: Box<TriggerEvent>,
/// table name
tbl_name: QualifiedName,
/// `FOR EACH ROW`
for_each_row: bool,
/// `WHEN`
when_clause: Option<Expr>,
when_clause: Option<Box<Expr>>,
/// statements
commands: Vec<TriggerCmd>,
},
@@ -168,7 +168,7 @@ pub enum Stmt {
/// `INDEXED`
indexed: Option<Indexed>,
/// `WHERE` clause
where_clause: Option<Expr>,
where_clause: Option<Box<Expr>>,
/// `RETURNING`
returning: Option<Vec<ResultColumn>>,
/// `ORDER BY`
@@ -217,7 +217,7 @@ pub enum Stmt {
/// `COLUMNS`
columns: Option<DistinctNames>,
/// `VALUES` or `SELECT`
body: InsertBody,
body: Box<InsertBody>,
/// `RETURNING`
returning: Option<Vec<ResultColumn>>,
},
@@ -256,7 +256,7 @@ pub enum Stmt {
/// `FROM`
from: Option<FromClause>,
/// `WHERE` clause
where_clause: Option<Expr>,
where_clause: Option<Box<Expr>>,
/// `RETURNING`
returning: Option<Vec<ResultColumn>>,
/// `ORDER BY`
@@ -1005,7 +1005,7 @@ pub struct GroupBy {
/// expressions
pub exprs: Vec<Expr>,
/// `HAVING`
pub having: Option<Expr>, // HAVING clause on a non-aggregate query
pub having: Option<Box<Expr>>, // HAVING clause on a non-aggregate query
}
/// identifier or one of several keywords or `INDEXED`
@@ -1769,9 +1769,9 @@ pub enum TransactionType {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Upsert {
/// conflict targets
pub index: Option<UpsertIndex>,
pub index: Option<Box<UpsertIndex>>,
/// `DO` clause
pub do_clause: UpsertDo,
pub do_clause: Box<UpsertDo>,
/// next upsert
pub next: Option<Box<Upsert>>,
}
@@ -1872,9 +1872,9 @@ pub enum FrameBound {
/// `CURRENT ROW`
CurrentRow,
/// `FOLLOWING`
Following(Expr),
Following(Box<Expr>),
/// `PRECEDING`
Preceding(Expr),
Preceding(Box<Expr>),
/// `UNBOUNDED FOLLOWING`
UnboundedFollowing,
/// `UNBOUNDED PRECEDING`

View File

@@ -731,7 +731,7 @@ nulls(A) ::= . {A = None;}
%type groupby_opt {Option<GroupBy>}
groupby_opt(A) ::= . {A = None;}
groupby_opt(A) ::= GROUP BY nexprlist(X) having_opt(Y). {A = Some(GroupBy{ exprs: X, having: Y });}
groupby_opt(A) ::= GROUP BY nexprlist(X) having_opt(Y). {A = Some(GroupBy{ exprs: X, having: Y.map(Box::new) });}
%type having_opt {Option<Expr>}
having_opt(A) ::= . {A = None;}
@@ -761,13 +761,13 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
cmd ::= with(C) DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W)
orderby_opt(O) limit_opt(L). {
let (where_clause, returning) = W;
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause, returning,
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause: where_clause.map(Box::new), returning,
order_by: O, limit: L });
}
%else
cmd ::= with(C) DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W). {
let (where_clause, returning) = W;
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause, returning,
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause: where_clause.map(Box::new), returning,
order_by: None, limit: None });
}
%endif
@@ -791,14 +791,14 @@ cmd ::= with(C) UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from
where_opt_ret(W) orderby_opt(O) limit_opt(L). {
let (where_clause, returning) = W;
self.ctx.stmt = Some(Stmt::Update { with: C, or_conflict: R, tbl_name: X, indexed: I, sets: Y, from: F,
where_clause, returning, order_by: O, limit: L });
where_clause: where_clause.map(Box::new), returning, order_by: O, limit: L });
}
%else
cmd ::= with(C) UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
where_opt_ret(W). {
let (where_clause, returning) = W;
self.ctx.stmt = Some(Stmt::Update { with: C, or_conflict: R, tbl_name: X, indexed: I, sets: Y, from: F,
where_clause, returning, order_by: None, limit: None });
where_clause: where_clause.map(Box::new), returning, order_by: None, limit: None });
}
%endif
@@ -828,13 +828,13 @@ cmd ::= with(W) insert_cmd(R) INTO xfullname(X) idlist_opt(F) select(S)
let (upsert, returning) = U;
let body = InsertBody::Select(Box::new(S), upsert);
self.ctx.stmt = Some(Stmt::Insert{ with: W, or_conflict: R, tbl_name: X, columns: F,
body, returning });
body: Box::new(body), returning });
}
cmd ::= with(W) insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES returning(Y).
{
let body = InsertBody::DefaultValues;
self.ctx.stmt = Some(Stmt::Insert{ with: W, or_conflict: R, tbl_name: X, columns: F,
body, returning: Y });
body: Box::new(body), returning: Y });
}
%type upsert {(Option<Upsert>, Option<Vec<ResultColumn>>)}
@@ -851,16 +851,16 @@ upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
{ let index = UpsertIndex{ targets: T, where_clause: TW };
let do_clause = UpsertDo::Set{ sets: Z, where_clause: W };
let (next, returning) = N;
A = (Some(Upsert{ index: Some(index), do_clause, next: next.map(Box::new) }), returning);}
A = (Some(Upsert{ index: Some(Box::new(index)), do_clause: Box::new(do_clause), next: next.map(Box::new) }), returning);}
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N).
{ let index = UpsertIndex{ targets: T, where_clause: TW };
let (next, returning) = N;
A = (Some(Upsert{ index: Some(index), do_clause: UpsertDo::Nothing, next: next.map(Box::new) }), returning); }
A = (Some(Upsert{ index: Some(Box::new(index)), do_clause: Box::new(UpsertDo::Nothing), next: next.map(Box::new) }), returning); }
upsert(A) ::= ON CONFLICT DO NOTHING returning(R).
{ A = (Some(Upsert{ index: None, do_clause: UpsertDo::Nothing, next: None }), R); }
{ A = (Some(Upsert{ index: None, do_clause: Box::new(UpsertDo::Nothing), next: None }), R); }
upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W) returning(R).
{ let do_clause = UpsertDo::Set{ sets: Z, where_clause: W };
A = (Some(Upsert{ index: None, do_clause, next: None }), R);}
A = (Some(Upsert{ index: None, do_clause: Box::new(do_clause), next: None }), R);}
%type returning {Option<Vec<ResultColumn>>}
returning(A) ::= RETURNING selcollist(X). {A = Some(X);}
@@ -1167,8 +1167,8 @@ minus_num(A) ::= MINUS number(X). {A = Expr::unary(UnaryOperator::Negative,
cmd ::= createkw temp(T) TRIGGER ifnotexists(NOERR) fullname(B) trigger_time(C) trigger_event(D)
ON fullname(E) foreach_clause(X) when_clause(G) BEGIN trigger_cmd_list(S) END. {
self.ctx.stmt = Some(Stmt::CreateTrigger{
temporary: T, if_not_exists: NOERR, trigger_name: B, time: C, event: D, tbl_name: E,
for_each_row: X, when_clause: G, commands: S
temporary: T, if_not_exists: NOERR, trigger_name: B, time: C, event: Box::new(D), tbl_name: E,
for_each_row: X, when_clause: G.map(Box::new), commands: S
});
}
@@ -1276,7 +1276,7 @@ cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
//////////////////////// ATTACH DATABASE file AS name /////////////////////////
%ifndef SQLITE_OMIT_ATTACH
cmd ::= ATTACH database_kw_opt expr(F) AS expr(D) key_opt(K). {
self.ctx.stmt = Some(Stmt::Attach{ expr: F, db_name: D, key: K });
self.ctx.stmt = Some(Stmt::Attach{ expr: Box::new(F), db_name: Box::new(D), key: K.map(Box::new) });
}
cmd ::= DETACH database_kw_opt expr(D). {
self.ctx.stmt = Some(Stmt::Detach(D));
@@ -1454,9 +1454,9 @@ frame_bound_s(A) ::= UNBOUNDED PRECEDING. {A = FrameBound::UnboundedPreceding;}
frame_bound_e(A) ::= frame_bound(X). {A = X;}
frame_bound_e(A) ::= UNBOUNDED FOLLOWING. {A = FrameBound::UnboundedFollowing;}
frame_bound(A) ::= expr(X) PRECEDING. { A = FrameBound::Preceding(X); }
frame_bound(A) ::= expr(X) PRECEDING. { A = FrameBound::Preceding(Box::new(X)); }
frame_bound(A) ::= CURRENT ROW. { A = FrameBound::CurrentRow; }
frame_bound(A) ::= expr(X) FOLLOWING. { A = FrameBound::Following(X); }
frame_bound(A) ::= expr(X) FOLLOWING. { A = FrameBound::Following(Box::new(X)); }
%type frame_exclude_opt {Option<FrameExclude>}
frame_exclude_opt(A) ::= . {A = None;}