Skip to content
52 changes: 52 additions & 0 deletions benchmarks/egress.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
QwpResultBatchDecoder,
writeQwpVarint,
} from "../packages/client-core/src/_qwp/_core";
import { random } from "./workloads";

const ROWS = 10_000;
let sink = 0;
Expand Down Expand Up @@ -72,6 +73,32 @@ function resultFrame(rowCount: number): Uint8Array {
return encodeQwpFrame(payload.toUint8Array(), QWP_FLAG_GORILLA, 1);
}

// Gorilla decoding cost depends on the delta-of-delta code widths, so on how
// irregular the intervals are.
const GORILLA_SERIES: Record<string, (next: () => number) => bigint> = {
// Every delta-of-delta is zero: one bit per value.
"constant 1 ms": () => 1_000n,
// 50 us of jitter either way: mostly 9-bit codes.
"jittered 1 ms": (next) => 1_000n + BigInt(Math.floor(next() * 101) - 50),
// Exponential gaps averaging 1 s: mostly 36-bit codes.
"irregular 1 s": (next) =>
BigInt(Math.floor(-Math.log(1 - next()) * 1_000_000)),
};

function timestampResultFrame(timestamps: readonly bigint[]): Uint8Array {
const payload = new QwpByteWriter();
payload.writeUint8(QWP_EGRESS_MESSAGE.RESULT_BATCH).writeBigUint64(1n);
writeQwpVarint(payload, 0); // batch sequence
writeString(payload, "bench_timestamps");
writeQwpVarint(payload, timestamps.length);
writeQwpVarint(payload, 1);
writeString(payload, "timestamp");
payload.writeUint8(QWP_COLUMN_TYPE.TIMESTAMP);
payload.writeUint8(0).writeUint8(1); // no nulls, Gorilla encoded
payload.writeBytes(encodeQwpGorilla(timestamps));
return encodeQwpFrame(payload.toUint8Array(), QWP_FLAG_GORILLA, 1);
}

// A standard Zstd frame containing a 100-row QWP INT result body.
const COMPRESSED_INT_RESULT_BODY = Uint8Array.from([
40, 181, 47, 253, 96, 153, 0, 157, 0, 0, 96, 0, 0, 0, 100, 1, 1, 120, 4, 0,
Expand Down Expand Up @@ -141,4 +168,29 @@ describe("QWP egress batch decoding", () => {
});
});

describe("QWP Gorilla timestamp decoding", () => {
for (const [name, interval] of Object.entries(GORILLA_SERIES)) {
const next = random(3);
let timestamp = 1_700_000_000_000_000n;
const message = decodeQwpEgressMessage(
timestampResultFrame(
Array.from({ length: ROWS }, () => (timestamp += interval(next))),
),
);
if (message.kind !== "result-batch") {
throw new Error("Gorilla benchmark frame is not a result batch");
}
// One long-lived decoder, as an egress session keeps.
const decoder = new QwpResultBatchDecoder();
bench(`materialized / ${name} / ${ROWS} rows`, () => {
decoder.resetQuerySchema();
sink += decoder.decode(message).rowCount;
});
bench(`column view / ${name} / ${ROWS} rows`, () => {
decoder.resetQuerySchema();
sink += decoder.decodeView(message).rowCount;
});
}
});

export const egressBenchmarkSink = (): number => sink;
2 changes: 1 addition & 1 deletion benchmarks/workloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface BenchmarkWorkload {
}

/** Deterministic, dependency-free xorshift32 generator. */
function random(seed: number): () => number {
export function random(seed: number): () => number {
let state = seed || 0x9e3779b9;
const next = (): number => {
state ^= state << 13;
Expand Down
Loading
Loading