Skip to content

Commit 5bb2cb4

Browse files
committed
Optimize primitive arg layout fast paths
1 parent 912663b commit 5bb2cb4

4 files changed

Lines changed: 294 additions & 28 deletions

File tree

core-relations/src/action/mod.rs

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
BaseValues, ContainerValues, ExternalFunctionId, WrappedTable,
2323
common::Value,
2424
free_join::{CounterId, Counters, ExternalFunctions, TableId, TableInfo, Variable},
25-
pool::{Clear, Pooled, with_pool_set},
25+
pool::{Clear, Pool, Pooled, with_pool_set},
2626
table_spec::{ColumnId, MutationBuffer},
2727
};
2828

@@ -55,6 +55,12 @@ impl From<Value> for QueryEntry {
5555
}
5656
}
5757

58+
#[derive(Debug, Clone)]
59+
pub(crate) enum ArgBindings {
60+
Owned(Vec<QueryEntry>),
61+
BorrowedWindow { start: Variable, len: usize },
62+
}
63+
5864
/// A value that can be written to a table in an action.
5965
#[derive(Debug, Clone, Copy)]
6066
pub enum WriteVal {
@@ -279,6 +285,75 @@ impl Bindings {
279285
}
280286
}
281287

288+
fn invoke_batch_from_args(
289+
this: &dyn crate::free_join::ExternalFunction,
290+
state: &mut ExecutionState,
291+
mask: &mut Mask,
292+
bindings: &mut Bindings,
293+
args: &ArgBindings,
294+
out_var: Variable,
295+
) {
296+
match args {
297+
ArgBindings::Owned(args) => invoke_batch(this, state, mask, bindings, args, out_var),
298+
ArgBindings::BorrowedWindow { start, len } => {
299+
let pool: Pool<Vec<Value>> = with_pool_set(|ps| ps.get_pool().clone());
300+
let mut out = pool.get();
301+
out.reserve(mask.len());
302+
let start_index = start.index();
303+
let arity = *len;
304+
let iter = mask.iter_dynamic(
305+
with_pool_set(crate::pool::PoolSet::get_pool),
306+
(0..arity)
307+
.map(|i| ValueSource::Slice(&bindings[Variable::from_usize(start_index + i)])),
308+
);
309+
iter.fill_vec(&mut out, Value::stale, |_, args| {
310+
this.invoke(state, args.as_slice())
311+
});
312+
bindings.insert(out_var, &out);
313+
}
314+
}
315+
}
316+
317+
fn invoke_batch_assign_from_args(
318+
this: &dyn crate::free_join::ExternalFunction,
319+
state: &mut ExecutionState,
320+
mask: &mut Mask,
321+
bindings: &mut Bindings,
322+
args: &ArgBindings,
323+
out_var: Variable,
324+
) {
325+
match args {
326+
ArgBindings::Owned(args) => invoke_batch_assign(this, state, mask, bindings, args, out_var),
327+
ArgBindings::BorrowedWindow { start, len } => {
328+
let mut out = bindings.take(out_var).expect("out_var must be bound");
329+
let start_index = start.index();
330+
let arity = *len;
331+
let iter = mask.iter_dynamic(
332+
with_pool_set(crate::pool::PoolSet::get_pool),
333+
(0..arity)
334+
.map(|i| ValueSource::Slice(&bindings[Variable::from_usize(start_index + i)])),
335+
);
336+
iter.assign_vec_and_retain(&mut out.vals, |_, args| this.invoke(state, &args));
337+
bindings.replace(out);
338+
}
339+
}
340+
}
341+
342+
fn single_external_borrowed_window(
343+
instrs: &[Instr],
344+
) -> Option<(ExternalFunctionId, Variable, usize, Variable)> {
345+
match instrs {
346+
[
347+
Instr::External {
348+
func,
349+
args: ArgBindings::BorrowedWindow { start, len },
350+
dst,
351+
},
352+
] => Some((*func, *start, *len, *dst)),
353+
_ => None,
354+
}
355+
}
356+
282357
/// A binding that has been extracted from a [`Bindings`] struct via the [`Bindings::take`] method.
283358
///
284359
/// This allows for a variable's contents to be read while the [`Bindings`] struct has been
@@ -603,6 +678,33 @@ impl ExecutionState<'_> {
603678
bindings.matches = 1;
604679
}
605680

681+
if let Some((func, start, len, dst)) = single_external_borrowed_window(instrs) {
682+
let matches = bindings.matches;
683+
let mut out: Pooled<Vec<Value>> = with_pool_set(|ps| ps.get());
684+
out.resize(matches, Value::stale());
685+
let mut transposed: Pooled<Vec<Value>> = with_pool_set(|ps| ps.get());
686+
transposed.resize(matches * len, Value::stale());
687+
let start_index = start.index();
688+
let mut succeeded = 0usize;
689+
for row in 0..matches {
690+
for col in 0..len {
691+
transposed[row * len + col] =
692+
bindings[Variable::from_usize(start_index + col)][row];
693+
}
694+
if self.should_stop() {
695+
break;
696+
}
697+
if let Some(value) =
698+
self.call_external_func(func, &transposed[row * len..(row + 1) * len])
699+
{
700+
out[row] = value;
701+
succeeded += 1;
702+
}
703+
}
704+
bindings.insert(dst, &out);
705+
return succeeded;
706+
}
707+
606708
// Vectorized execution for larger batch sizes
607709
let mut mask = with_pool_set(|ps| Mask::new(0..bindings.matches, ps));
608710
for instr in instrs {
@@ -758,7 +860,7 @@ impl ExecutionState<'_> {
758860
}
759861

760862
// Call the given external function on all entries where the lookup failed.
761-
invoke_batch_assign(
863+
invoke_batch_assign_from_args(
762864
self.db.external_funcs[*func].as_ref(),
763865
self,
764866
&mut to_call_func,
@@ -817,7 +919,7 @@ impl ExecutionState<'_> {
817919
});
818920
}
819921
Instr::External { func, args, dst } => {
820-
invoke_batch(
922+
invoke_batch_from_args(
821923
self.db.external_funcs[*func].as_ref(),
822924
self,
823925
mask,
@@ -834,7 +936,7 @@ impl ExecutionState<'_> {
834936
dst,
835937
} => {
836938
let mut f1_result = mask.clone();
837-
invoke_batch(
939+
invoke_batch_from_args(
838940
self.db.external_funcs[*f1].as_ref(),
839941
self,
840942
&mut f1_result,
@@ -848,7 +950,7 @@ impl ExecutionState<'_> {
848950
return;
849951
}
850952
// Call the given external function on all entries where the first call failed.
851-
invoke_batch_assign(
953+
invoke_batch_assign_from_args(
852954
self.db.external_funcs[*f2].as_ref(),
853955
self,
854956
&mut to_call_f2,
@@ -921,7 +1023,7 @@ pub(crate) enum Instr {
9211023
table: TableId,
9221024
table_key: Vec<QueryEntry>,
9231025
func: ExternalFunctionId,
924-
func_args: Vec<QueryEntry>,
1026+
func_args: ArgBindings,
9251027
dst_col: ColumnId,
9261028
dst_var: Variable,
9271029
},
@@ -950,7 +1052,7 @@ pub(crate) enum Instr {
9501052
/// Bind the result of the external function to a variable.
9511053
External {
9521054
func: ExternalFunctionId,
953-
args: Vec<QueryEntry>,
1055+
args: ArgBindings,
9541056
dst: Variable,
9551057
},
9561058

@@ -959,9 +1061,9 @@ pub(crate) enum Instr {
9591061
/// single failure of `External`).
9601062
ExternalWithFallback {
9611063
f1: ExternalFunctionId,
962-
args1: Vec<QueryEntry>,
1064+
args1: ArgBindings,
9631065
f2: ExternalFunctionId,
964-
args2: Vec<QueryEntry>,
1066+
args2: ArgBindings,
9651067
dst: Variable,
9661068
},
9671069

core-relations/src/action/tests.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::{
22
action::mask::{IterResult, ValueSource},
3+
free_join::Variable,
4+
numeric_id::NumericId,
35
pool::{PoolSet, with_pool_set},
46
};
57

@@ -148,3 +150,40 @@ fn test_early_stop_multiple_clones() {
148150
assert!(state2.should_stop());
149151
assert!(state3.should_stop());
150152
}
153+
154+
#[test]
155+
fn run_instrs_single_external_borrowed_window_executes() {
156+
let mut db = crate::free_join::Database::default();
157+
let sum = db.add_external_function(Box::new(crate::make_external_func(|_, args| {
158+
let [x, y] = args else { panic!() };
159+
Some(crate::common::Value::from_usize(
160+
(x.rep() + y.rep()) as usize,
161+
))
162+
})));
163+
let mut state = crate::action::ExecutionState::new(db.read_only_view(), Default::default());
164+
let mut bindings = super::Bindings::new(8);
165+
bindings.insert(
166+
Variable::from_usize(0),
167+
&[crate::table_shortcuts::v(1), crate::table_shortcuts::v(2)],
168+
);
169+
bindings.insert(
170+
Variable::from_usize(1),
171+
&[crate::table_shortcuts::v(10), crate::table_shortcuts::v(20)],
172+
);
173+
174+
let instrs = vec![super::Instr::External {
175+
func: sum,
176+
args: super::ArgBindings::BorrowedWindow {
177+
start: Variable::from_usize(0),
178+
len: 2,
179+
},
180+
dst: Variable::from_usize(2),
181+
}];
182+
183+
let succeeded = state.run_instrs(&instrs, &mut bindings);
184+
assert_eq!(succeeded, 2);
185+
assert_eq!(
186+
&bindings[Variable::from_usize(2)],
187+
&[crate::table_shortcuts::v(11), crate::table_shortcuts::v(22)]
188+
);
189+
}

core-relations/src/query.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use thiserror::Error;
88

99
use crate::{
1010
BaseValueId, CounterId, ExternalFunctionId, PoolSet,
11-
action::{Instr, QueryEntry, WriteVal},
11+
action::{ArgBindings, Instr, QueryEntry, WriteVal},
1212
common::HashMap,
1313
free_join::{
1414
ActionId, AtomId, Database, ProcessedConstraints, SubAtom, TableId, TableInfo, VarInfo,
@@ -243,6 +243,18 @@ impl<'outer, 'a> QueryBuilder<'outer, 'a> {
243243
}
244244
}
245245

246+
fn mark_used_arg_bindings(&mut self, entries: &ArgBindings) {
247+
match entries {
248+
ArgBindings::Owned(entries) => self.mark_used(entries),
249+
ArgBindings::BorrowedWindow { start, len } => {
250+
for offset in 0..*len {
251+
self.query.var_info[Variable::from_usize(start.index() + offset)].used_in_rhs =
252+
true;
253+
}
254+
}
255+
}
256+
}
257+
246258
fn mark_defined(&mut self, entry: &QueryEntry) {
247259
// TODO: use some of this information in query planning, e.g. dedup at match time.
248260
if let QueryEntry::Var(v) = entry {
@@ -622,14 +634,32 @@ impl RuleBuilder<'_, '_> {
622634
let res = self.qb.new_var();
623635
self.qb.instrs.push(Instr::External {
624636
func,
625-
args: args.to_vec(),
637+
args: ArgBindings::Owned(args.to_vec()),
626638
dst: res,
627639
});
628640
self.qb.mark_used(args);
629641
self.qb.mark_defined(&res.into());
630642
Ok(res)
631643
}
632644

645+
pub fn call_external_window(
646+
&mut self,
647+
func: ExternalFunctionId,
648+
start: Variable,
649+
len: usize,
650+
) -> Result<Variable, QueryError> {
651+
let res = self.qb.new_var();
652+
let args = ArgBindings::BorrowedWindow { start, len };
653+
self.qb.instrs.push(Instr::External {
654+
func,
655+
args: args.clone(),
656+
dst: res,
657+
});
658+
self.qb.mark_used_arg_bindings(&args);
659+
self.qb.mark_defined(&res.into());
660+
Ok(res)
661+
}
662+
633663
/// Look up the given key in the given table. If the lookup fails, then call the given external
634664
/// function with the given arguments. Bind the result to the returned variable. If the
635665
/// external function returns None (and the lookup fails) then the execution of the rule halts.
@@ -648,7 +678,7 @@ impl RuleBuilder<'_, '_> {
648678
table,
649679
table_key: key.to_vec(),
650680
func,
651-
func_args: func_args.to_vec(),
681+
func_args: ArgBindings::Owned(func_args.to_vec()),
652682
dst_var: res,
653683
dst_col,
654684
});
@@ -668,9 +698,9 @@ impl RuleBuilder<'_, '_> {
668698
let res = self.qb.new_var();
669699
self.qb.instrs.push(Instr::ExternalWithFallback {
670700
f1,
671-
args1: args1.to_vec(),
701+
args1: ArgBindings::Owned(args1.to_vec()),
672702
f2,
673-
args2: args2.to_vec(),
703+
args2: ArgBindings::Owned(args2.to_vec()),
674704
dst: res,
675705
});
676706
self.qb.mark_used(args1);
@@ -679,6 +709,32 @@ impl RuleBuilder<'_, '_> {
679709
Ok(res)
680710
}
681711

712+
pub fn call_external_with_fallback_window(
713+
&mut self,
714+
f1: ExternalFunctionId,
715+
start1: Variable,
716+
len1: usize,
717+
f2: ExternalFunctionId,
718+
args2: &[QueryEntry],
719+
) -> Result<Variable, QueryError> {
720+
let res = self.qb.new_var();
721+
let args1 = ArgBindings::BorrowedWindow {
722+
start: start1,
723+
len: len1,
724+
};
725+
self.qb.instrs.push(Instr::ExternalWithFallback {
726+
f1,
727+
args1: args1.clone(),
728+
f2,
729+
args2: ArgBindings::Owned(args2.to_vec()),
730+
dst: res,
731+
});
732+
self.qb.mark_used_arg_bindings(&args1);
733+
self.qb.mark_used(args2);
734+
self.qb.mark_defined(&res.into());
735+
Ok(res)
736+
}
737+
682738
/// Continue execution iff the two arguments are equal.
683739
pub fn assert_eq(&mut self, l: QueryEntry, r: QueryEntry) {
684740
self.qb.instrs.push(Instr::AssertEq(l, r));

0 commit comments

Comments
 (0)