Skip to content

Commit fbf7a25

Browse files
committed
perf: hoist TextEncoder/TextDecoder to module level in readers
Inline `new TextDecoder()` / `new TextEncoder()` allocations replaced with module-level singletons in reader.ts, csv-reader.ts, json-reader.ts, and arrow-reader.ts — avoids per-call allocation during ingest.
1 parent 3af0679 commit fbf7a25

File tree

4 files changed

+15
-9
lines changed

4 files changed

+15
-9
lines changed

src/reader.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import type { ColumnMeta, DataType } from "./types.js";
1010
import type { FragmentSource } from "./operators.js";
1111

12+
const textEncoder = new TextEncoder();
13+
1214
// ---------------------------------------------------------------------------
1315
// FormatReader — one per file format (CSV, JSON, Arrow IPC, ...)
1416
// ---------------------------------------------------------------------------
@@ -238,12 +240,11 @@ export function encodeColumnBuffer(
238240
}
239241
case "utf8":
240242
case "binary": {
241-
const encoder = new TextEncoder();
242243
const parts: Uint8Array[] = [];
243244
let totalLen = 0;
244245
for (const v of values) {
245246
const str = v === null ? "" : String(v);
246-
const encoded = encoder.encode(str);
247+
const encoded = textEncoder.encode(str);
247248
const header = new Uint8Array(4);
248249
new DataView(header.buffer).setUint32(0, encoded.length, true);
249250
parts.push(header, encoded);

src/readers/arrow-reader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ function fbOffset(buf: DataView, tableOffset: number, fieldIdx: number): number
5959
return tableOffset + fieldOff;
6060
}
6161

62+
const textDecoder = new TextDecoder();
63+
6264
/** Read a flatbuffer string. */
6365
function fbString(buf: DataView, strOffset: number): string {
6466
const len = readI32(buf, strOffset);
6567
const start = strOffset + 4;
6668
const bytes = new Uint8Array(buf.buffer, buf.byteOffset + start, len);
67-
return new TextDecoder().decode(bytes);
69+
return textDecoder.decode(bytes);
6870
}
6971

7072
/** Read a flatbuffer vector length. */

src/readers/csv-reader.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { type FormatReader, type DataSource, encodeColumnBuffer } from "../reade
1313
import type { ColumnMeta, DataType, PageInfo, Row } from "../types.js";
1414
import type { FragmentSource } from "../operators.js";
1515

16+
const textDecoder = new TextDecoder("utf-8");
17+
1618
// ---------------------------------------------------------------------------
1719
// CSV Parser Helpers
1820
// ---------------------------------------------------------------------------
@@ -337,8 +339,7 @@ export class CsvReader implements FormatReader {
337339
if (first === 0x5b || first === 0x7b) return false;
338340

339341
// Decode a chunk of text and check for a delimiter pattern
340-
const decoder = new TextDecoder("utf-8");
341-
const sample = decoder.decode(head.slice(0, Math.min(head.length, 2048)));
342+
const sample = textDecoder.decode(head.slice(0, Math.min(head.length, 2048)));
342343
const firstLine = sample.split(/\r?\n/)[0] ?? "";
343344
if (firstLine.length === 0) return false;
344345

@@ -349,15 +350,15 @@ export class CsvReader implements FormatReader {
349350

350351
async readMeta(source: DataSource): Promise<{ columns: ColumnMeta[]; totalRows: number }> {
351352
const buf = await source.readAll();
352-
const text = new TextDecoder().decode(buf);
353+
const text = textDecoder.decode(buf);
353354
const parsed = parseCsvFull(text);
354355
const columns = buildColumnMeta(parsed);
355356
return { columns, totalRows: parsed.rowCount };
356357
}
357358

358359
async createFragments(source: DataSource, projected: ColumnMeta[]): Promise<FragmentSource[]> {
359360
const buf = await source.readAll();
360-
const text = new TextDecoder().decode(buf);
361+
const text = textDecoder.decode(buf);
361362
const parsed = parseCsvFull(text);
362363
// Build full column meta so the fragment has the full schema
363364
const allMeta = buildColumnMeta(parsed);

src/readers/json-reader.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { type FormatReader, type DataSource, encodeColumnBuffer } from "../reade
1212
import type { ColumnMeta, DataType, PageInfo, Row } from "../types.js";
1313
import type { FragmentSource } from "../operators.js";
1414

15+
const textDecoder = new TextDecoder();
16+
1517
// ---------------------------------------------------------------------------
1618
// Helpers
1719
// ---------------------------------------------------------------------------
@@ -282,15 +284,15 @@ export class JsonReader implements FormatReader {
282284

283285
async readMeta(source: DataSource): Promise<{ columns: ColumnMeta[]; totalRows: number }> {
284286
const buf = await source.readAll();
285-
const text = new TextDecoder().decode(buf);
287+
const text = textDecoder.decode(buf);
286288
const parsed = parseJsonFull(text);
287289
const columns = buildColumnMeta(parsed);
288290
return { columns, totalRows: parsed.rowCount };
289291
}
290292

291293
async createFragments(source: DataSource, projected: ColumnMeta[]): Promise<FragmentSource[]> {
292294
const buf = await source.readAll();
293-
const text = new TextDecoder().decode(buf);
295+
const text = textDecoder.decode(buf);
294296
const parsed = parseJsonFull(text);
295297
const allMeta = buildColumnMeta(parsed);
296298
const projectedNames = new Set(projected.map(c => c.name));

0 commit comments

Comments
 (0)