Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions packages/zql/src/ivm/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,24 @@ export function normalizeUndefined(v: Value): NormalizedValue {
export type Comparator = (r1: Row, r2: Row) => number;

export function makeComparator(order: Ordering, reverse?: boolean): Comparator {
// A single ascending field is the common shape -- it is what every source
// ordered by a single-column primary key gets -- and specializing it drops
// the loop, the direction test and the reverse test from the hottest
// function in hydration.
if (order.length === 1 && order[0][1] === 'asc' && !reverse) {
const field = order[0][0];
return (a, b) => compareValues(a[field], b[field]);
}

const length = order.length;
return (a, b) => {
// Skip destructuring here since it is hot code.
for (const ord of order) {
const field = ord[0];
const comp = compareValues(a[field], b[field]);
// Skip destructuring here since it is hot code. An indexed loop rather
// than `for...of` for the same reason: Hermes allocates an iterator and
// calls `next()` per element, which costs more than the comparison for an
// ordering this short.
for (let i = 0; i < length; i++) {
const ord = order[i];
const comp = compareValues(a[ord[0]], b[ord[0]]);
if (comp !== 0) {
const result = ord[1] === 'asc' ? comp : -comp;
return reverse ? -result : result;
Expand Down
24 changes: 22 additions & 2 deletions packages/zql/src/ivm/view-apply-change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,11 +768,31 @@ function binarySearch(
target: Row,
comparator: Comparator,
): number {
let low = 0;
let high = view.length - 1;
if (high < 0) {
return ~0;
}

// Probe the last entry before searching. Hydration feeds the view rows in
// the query's sort order, so every insert belongs at the end and the plain
// search spends log2(n) comparisons to rediscover that -- about eleven per
// row for a view of a couple of thousand. Row comparison is the single
// largest cost in hydration on Hermes, so collapsing those eleven to one is
// worth the one extra comparison this costs when the row does land inside
// the view, which is a single push rather than a bulk load.
// MetaEntry has all Row props; comparator only reads string keys
const last = comparator(view[high] as Row, target);
if (last < 0) {
return ~(high + 1);
}
if (last === 0) {
return high;
}

let low = 0;
high -= 1;
while (low <= high) {
const mid = (low + high) >>> 1;
// MetaEntry has all Row props; comparator only reads string keys
const comparison = comparator(view[mid] as Row, target);
if (comparison < 0) {
low = mid + 1;
Expand Down
Loading