Benchmarks: pond-ts vs pondjs
pond-ts is a ground-up TypeScript rewrite of pondjs. This page documents how the two libraries compare across all shared core operations.
Summary
pond-ts wins every benchmark — 54 out of 54 operations tested, across three data sizes (1k, 4k, 16k events). The geometric mean speedup is 7.6x, and the advantage grows with data size.
| Category | Speedup at N=16k | Key architectural difference |
|---|---|---|
| Aggregation | 25–32x | O(N+B) single-pass bucketing vs O(N×B) Pipeline |
| Alignment | 32x | Forward cursor vs repeated binary search |
| Rate/diff | 18x | Direct array walk vs Pipeline materialization |
| Fill | 10–11x | Single-pass per strategy vs Pipeline per column |
| Transforms | 3–16x | Pre-validated constructor skips re-validation |
| Construction | 7x | Plain frozen objects vs ImmutableJS wrapping |
| Statistics | 7–9x | Direct array operations vs ImmutableJS iteration |
| Serialization | 4x | Lightweight internal representation |
| Event access | 23x | Array indexing vs ImmutableJS get() |
Zero regressions were found. The narrowest gap is map() at N=1000 (1.6x),
still a clear win.
Why it's faster
Three architectural decisions account for most of the improvement:
1. No Pipeline overhead
pondjs routes every operation through a Pipeline abstraction — even simple
transforms like select() or rename() construct a pipeline, push events
through observable nodes, and collect output into keyed collections. This is
flexible but adds constant overhead per event that dominates at scale.
pond-ts operates directly on arrays. Each transform walks the event array once, applies the operation, and passes results to a pre-validated constructor that skips redundant schema checks.
2. No ImmutableJS
pondjs wraps event data in ImmutableJS maps. Every get() call, every
setData(), and every event construction pays for ImmutableJS overhead.
pond-ts uses plain frozen JavaScript objects. Object.freeze() provides the
same immutability guarantee at near-zero cost. Event access is a property
lookup, not a map traversal.
3. Algorithmic improvements
Several operations were re-implemented with better time complexity:
- Aggregation: O(N+B) single-pass bucketing instead of O(N×B) window scanning per bucket.
- Rolling windows: O(N) sliding window with incremental add/remove instead of O(N²) recomputation.
- Alignment: Forward cursor that advances through the source array instead of binary search per output point.
includesKey(): O(log N) bisect instead of O(N) linear scan.
Running the benchmarks
npm run build
node bench/vs-pondjs.cjs
The benchmark script measures median execution time over 20 iterations (with 3 warmup rounds) at N = 1000, 4000, and 16000 events.
Detailed results
Construction
Operation N pondjs (ms) pond-ts (ms) Speedup
new TimeSeries() 1000 0.94 0.23 4.1x
new TimeSeries() 4000 2.91 0.53 5.5x
new TimeSeries() 16000 14.36 2.17 6.6x
Construction speedup grows with N because pondjs wraps each event's data in an ImmutableJS map during construction.
Aggregation
Operation N pondjs (ms) pond-ts (ms) Speedup
aggregate(10s, avg) 1000 1.67 0.19 8.9x
aggregate(1m, sum) 1000 0.91 0.08 11.0x
aggregate(10s, avg+max+min) 1000 1.86 0.20 9.1x
aggregate(10s, avg) 4000 4.89 0.20 24.9x
aggregate(1m, sum) 4000 3.86 0.12 31.2x
aggregate(10s, avg+max+min) 4000 7.09 0.34 21.2x
aggregate(10s, avg) 16000 20.35 0.83 24.7x
aggregate(1m, sum) 16000 15.39 0.49 31.5x
aggregate(10s, avg+max+min) 16000 32.41 1.33 24.5x
The largest speedups in the suite. pond-ts's O(N+B) bucket assignment means
the number of buckets barely affects total time. pondjs's Pipeline-based
fixedWindowRollup does more work per event.
Rate
Operation N pondjs (ms) pond-ts (ms) Speedup
rate(value) 1000 1.06 0.27 4.0x
rate(value) 4000 5.26 0.35 15.1x
rate(value) 16000 23.81 1.35 17.7x
pondjs routes rate() through the Pipeline. pond-ts walks the array once,
computing deltas in place.
Fill
Operation N pondjs (ms) pond-ts (ms) Speedup
fill(hold/pad) 1000 0.67 0.29 2.3x
fill(zero) 1000 0.66 0.30 2.2x
fill(linear) 1000 0.65 0.31 2.1x
fill(hold/pad) 4000 3.02 0.81 3.7x
fill(zero) 4000 3.05 0.29 10.4x
fill(linear) 4000 3.09 0.29 10.5x
fill(hold/pad) 16000 12.55 1.20 10.5x
fill(zero) 16000 12.27 1.19 10.3x
fill(linear) 16000 12.66 1.18 10.7x
pondjs's fill() constructs a Pipeline per call. For linear fill with
multiple columns, it constructs a separate Pipeline per column. pond-ts handles
all strategies in a single pass.
Transforms
Operation N pondjs (ms) pond-ts (ms) Speedup
select(value) 1000 0.75 0.14 5.3x
map(x*2) 1000 0.73 0.44 1.6x
collapse(a+b+c, sum) 1000 1.17 0.27 4.3x
rename(value→measurement) 1000 0.82 0.20 4.2x
select(value) 4000 3.68 0.26 14.2x
map(x*2) 4000 3.43 1.02 3.4x
collapse(a+b+c, sum) 4000 5.09 0.83 6.1x
rename(value→measurement) 4000 4.28 0.45 9.6x
select(value) 16000 16.07 1.01 15.9x
map(x*2) 16000 15.39 4.79 3.2x
collapse(a+b+c, sum) 16000 23.40 3.63 6.4x
rename(value→measurement) 16000 21.43 1.67 12.8x
map() shows the narrowest gap because both libraries must call a user-provided
function per event. The difference is entirely in construction overhead: pond-ts
uses a pre-validated internal constructor path for derived series.
Alignment
Operation N pondjs (ms) pond-ts (ms) Speedup
align(5s, linear) 1000 1.10 0.13 8.8x
align(5s, linear) 4000 4.19 0.17 24.7x
align(10s, linear) 16000 14.13 0.44 32.3x
pond-ts uses a forward cursor that advances through the source array in sync with the output sequence. pondjs uses binary search per output point, giving O(M log N) vs pond-ts's O(N + M).
Event access
Operation N pondjs (ms) pond-ts (ms) Speedup
at(i).get() full scan 1000 0.23 0.08 2.9x
at(i).get() full scan 4000 0.59 0.17 3.5x
at(i).get() full scan 16000 2.55 0.11 22.9x
Pure data access. The gap is ImmutableJS get() vs plain property lookup.
Serialization
Operation N pondjs (ms) pond-ts (ms) Speedup
toJSON() 1000 0.41 0.14 3.0x
toJSON() 4000 1.27 0.28 4.5x
toJSON() 16000 5.70 1.31 4.4x
Chained operations
Operation N pondjs (ms) pond-ts (ms) Speedup
map → select 1000 1.52 0.35 4.3x
map → select 4000 7.27 1.23 5.9x
map → select 16000 32.25 6.31 5.1x
Each step in pondjs creates a new Pipeline and materializes a new collection. pond-ts chains derived constructors without re-validating the output.
Statistics
Operation N pondjs (ms) pond-ts (ms) Speedup
median(value) 1000 0.29 0.07 4.3x
stdev(value) 1000 0.23 0.07 3.5x
median(value) 4000 1.65 0.25 6.5x
stdev(value) 4000 1.07 0.14 7.9x
median(value) 16000 9.70 1.31 7.4x
stdev(value) 16000 5.22 0.57 9.1x
Capabilities only in pond-ts
Beyond performance, pond-ts adds functionality that pondjs does not have:
- Live streaming:
LiveSeries,LiveView,LiveAggregation,LiveRollingAggregation - Live composition: chain
filter → diff → fill → aggregateon streaming data filter()as a first-class TimeSeries method (pondjs requires Pipeline)diff(),pctChange(),cumulative(),shift()columnar primitivesgroupBy()with optional transform callbackbfill(backward fill) strategy- TypeScript-first schema types that flow through every operation