Skip to content

Commit d6127c2

Browse files
committed
perf: ScanOperator hot-path allocation reduction in nextColumnar
- Cache filterCols/projCols per fragment instead of .filter() per page - Merge proj columns into filter Map directly instead of spread operator (avoids two intermediate arrays + new Map construction per matched page)
1 parent c8ca585 commit d6127c2

File tree

1 file changed

+19
-4
lines changed

1 file changed

+19
-4
lines changed

src/operators.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ export class ScanOperator implements Operator {
159159
private prefetchFragIdx = -1;
160160
private prefetchSkipped = 0;
161161
private _filterColNames: Set<string> | null = null;
162+
/** Cached per-fragment column splits (avoids .filter() per page on hot path). */
163+
private _cachedFragIdx = -1;
164+
private _cachedFilterCols: ColumnMeta[] = [];
165+
private _cachedProjCols: ColumnMeta[] = [];
162166

163167
constructor(fragments: FragmentSource[], query: QueryDescriptor, wasm: WasmEngine, applyFilters = false) {
164168
this.fragments = fragments;
@@ -286,8 +290,18 @@ export class ScanOperator implements Operator {
286290
const hasFilters = allFilters.length > 0 || (allFilterGroups && allFilterGroups.length > 0);
287291
if (this.filtersApplied && hasFilters) {
288292
const fcn = this._filterColNames!;
289-
const filterCols = frag.columns.filter(c => fcn.has(c.name));
290-
const projCols = frag.columns.filter(c => !fcn.has(c.name));
293+
// Cache column splits per fragment (avoids repeated .filter() per page)
294+
if (this._cachedFragIdx !== this.fragIdx) {
295+
this._cachedFragIdx = this.fragIdx;
296+
this._cachedFilterCols = [];
297+
this._cachedProjCols = [];
298+
for (const c of frag.columns) {
299+
if (fcn.has(c.name)) this._cachedFilterCols.push(c);
300+
else this._cachedProjCols.push(c);
301+
}
302+
}
303+
const filterCols = this._cachedFilterCols;
304+
const projCols = this._cachedProjCols;
291305

292306
// Phase 1: Fetch + decode only filter columns
293307
let filterPageMap: Map<string, { buf: ArrayBuffer; pageInfo: PageInfo }>;
@@ -329,11 +343,12 @@ export class ScanOperator implements Operator {
329343
projDecoded = new Map<string, DecodedValue[]>();
330344
}
331345

332-
const allDecoded = new Map([...filterDecoded, ...projDecoded]);
346+
// Merge proj columns into filter map (avoids Map spread allocation)
347+
for (const [k, v] of projDecoded) filterDecoded.set(k, v);
333348
this.scanMs += Date.now() - scanStart;
334349

335350
if (matchingIndices.length > 0) {
336-
return { columns: allDecoded, rowCount, selection: matchingIndices };
351+
return { columns: filterDecoded, rowCount, selection: matchingIndices };
337352
}
338353
continue;
339354
}

0 commit comments

Comments
 (0)